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
That prints a public
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: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.If you get an error here
If you get an error here
webhook_url_invalid— the URL must start withhttps://.webhook_url_not_public— Orbit could not reach it. Alocalhostaddress, 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.If delivered is false
If delivered is false
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 That returns straight away with a
"webhooks": true to any search and the messages start flowing.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 withGET /v3/search/{search_id}, which returns every result as it always has. The final search.completed message tells you when this applied:
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 aseenset. - Reply fast. Answer
200within 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_attells 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: sendinclude_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.