Skip to main content
A search takes as long as the profiles take to build. Polling GET /v3/search/{search_id} suits a page someone is watching; a background job is better served by a push. Orbit sends each message as soon as it commits the change, so you hear about a person whether or not you poll. Register a URL once and Orbit POSTs to it every time a result moves forward, carrying that person’s whole profile — the same object a profile read returns. One final delivery closes the search out. By the end of this guide you will have that running end to end. You need an API key with the search:read, profile:read, and webhooks:write scopes (get one here).
1

Get a public URL for your server

Orbit has to be able to reach your server over the public internet, so http://localhost:3000 will be rejected — Orbit refuses http:// URLs and anything pointing at localhost, .local, or a private network address.On a deployed server you already have a public URL, so skip ahead. While building on your laptop, use a tunnel — ngrok is the common one:
That prints a public https:// address that forwards to your local port. Copy it; you’ll use it in the next step and it stays valid while ngrok runs.
2

Tell Orbit where to send things

Register your URL, and say which messages you want.
The response contains a secret that starts with whsec_. Save it now — it is shown once and never again. Put it in your environment as ORBIT_WEBHOOK_SECRET. It is what proves an incoming request really came from Orbit rather than from someone who guessed your URL.Also keep the id from the response — that’s your WEBHOOK_ID for the test step below.
  • webhook_url_invalid — the URL must start with https://.
  • webhook_url_not_public — Orbit could not reach it. A localhost address, a private IP, or a tunnel that has stopped running.
3

Write the handler

Here is a complete, working server. Copy it as-is and it runs — the only thing you need to change is what you do with the data at the bottom.
4

Check it works, before running a real search

Send yourself a fake message. This posts one straight away, signed exactly like a real one, and tells you what your server replied.
Look for "delivered": true in the response and a line in your server’s console. Test messages carry "test": true and use made-up data, so they are safe to fire as often as you like — see Send a test delivery.
The error field tells you what happened. http_401 means your signature check rejected it — usually the wrong ORBIT_WEBHOOK_SECRET, or express.json parsing the body before the check. http_404 means the path in your registered URL does not match your route. A connection error means Orbit could not reach the URL at all.
5

Run a search

Add "webhooks": true to any search and the messages start flowing.
That returns straight away with a search_id. You do not need to do anything else with it — watch your console instead.Send include_profile: false when you use webhooks. The messages carry each full profile regardless, so you lose nothing, and it keeps both the immediate response and Orbit’s own work small. With include_profile: true Orbit rebuilds every result’s profile each time any one of them changes, which on a large search is work neither side needs.

What arrives, and when

Every message looks like this:
data is where everything lives — the full field list is on Webhook events. A message goes out whenever Orbit saves something new for a person: the first profile it publishes for them, each web-search segment that adds sections, and the photos that land with a save. That usually means two or three messages per person: Then one search.completed at the very end. Use result.profile_id as your key and overwrite that person’s row each time a message about them arrives.

How many people you hear about

One search’s messages cover its first 100 people, in the order the search found them. That order never changes between polls, so the same 100 people are covered from start to finish. A search that finds more than 100 is not truncated — only the messages are. Read the rest with GET /v3/search/{search_id}, which returns every result as it always has. The final search.completed message tells you when this applied:
The counts describe the whole search. results lists the people the messages covered. A search inside the cap carries neither results_truncated nor results_limit. The cap exists because every message carries a whole profile. Without it, one large search could push far more data at your server than it asked for.

Things worth knowing

  • Messages can arrive twice. If your server is slow or returns an error, Orbit retries — up to 6 times, spacing the attempts further apart each time. Retries reuse the same x-orbit-webhook-event-id, which is why the example keeps a seen set.
  • Reply fast. Answer 200 within 10 seconds and do the real work afterwards, as the example does. A handler that does slow work before replying will time out and get retried unnecessarily.
  • Always check the signature. Your URL is reachable by anyone who learns it. The check in the example is the thing that makes an incoming message trustworthy.
  • Order is not guaranteed. Retries can land out of order. data.occurred_at tells you when each change actually happened — compare it before overwriting newer data with older.
  • If your server is down for a while, Orbit keeps retrying through the backoff window. After 20 failures in a row an endpoint is switched off; register a new one to start receiving again. An endpoint receives the messages that happen after you register it, so a search still in progress carries on reaching you — including the people it had already finished — while a search that had already ended needs a rerun.
  • You can send it to Slack. Register a Slack incoming-webhook URL instead and you get a readable message in a channel rather than JSON. Handy for a notifications channel alongside your real handler.

Costs

Nothing extra. The search costs what it always costs — per person, at the depth reached. Messages are free, and a person who generates three of them costs the same as one who generates none. One setting does change how hard Orbit works, though not what you pay: send include_profile: false, as the step above does. A search that also asks for profiles in its HTTP response rebuilds all of them whenever any one person changes.

Turning it off

Only searches sent with "webhooks": true produce messages, so leaving the field out is enough for a one-off. To stop entirely, delete the endpoint. This also means one API key can serve both an app where someone is watching a page (poll those, no webhooks field) and a background pipeline (push those). Polling still works on a search that pushes, so you can run both while you migrate.