Skip to main content
Every delivery is an HTTPS POST with a JSON body and three headers: Failed deliveries are retried up to 6 times with backoff (10-second timeout per attempt). Respond with any 2xx quickly and process asynchronously. An endpoint that fails 20 consecutive deliveries is automatically disabled. To exercise your handler before a real event arrives, send yourself a test delivery — same headers and signature, with "test": true in the envelope.

Delivery Body

Every delivery uses the same envelope. id matches x-orbit-webhook-event-id, created is the signed Unix timestamp, and the event payload documented below is in data:

Verifying Signatures

Compute the signature from the raw request body, compare in constant time, and reject deliveries with a timestamp older than five minutes. This protects you from replayed deliveries:
JavaScript
Python

profile.updated

Sent when a watcher run finds new information about a profile: new timeline events, a changed profile section, a new source, or new social activity. The delivery carries the events themselves: events holds the new timeline entries in the exact shape profile reads return in sections.eventsTimeline — title, description, date with datePrecision and dateBasis, and the sources behind the event — so your handler has everything without a follow-up read, and a later profile read returns identical objects. newEventCount is the number of new events; changes lists the run’s changes, including one section_updated entry per section the run changed. runsCovered names how many completed runs the delivery folds together (runId is the newest), and updatedSections lists events_timeline when the delivery carries events plus the name of every section the covered runs changed. The data object:
events carries at most 10 entries and changes at most 25, with section changes listed first; the full timeline lives on profile reads and the full change set on watcher runs. Updates are reported through the fields where they appear on the profile: a job update that adds a job and a timeline event arrives as one section_updated change and one event.
Slack incoming-webhook URLs receive a formatted Slack message (new-event count and a short change list) instead of this JSON. Use one for a notifications channel.

search.profile.updated

Sent each time a result of a search started with "webhooks": true advances a generation step, carrying that person’s whole profile. Orbit sends it when it commits the change, so a delivery does not wait on a poll. It is the push half of Get search status: a handler subscribed to it receives the same rows a poll returns, at the moment they change, so a search that builds profiles over several minutes reaches you without a poll loop. See Push search results for the full loop. One delivery covers one result. result is the result row exactly as GET /v3/search/{search_id} returns it, with profile embedded — the same object a profile read returns, whatever the search’s own include_profile setting was. The surrounding fields carry the search’s progress at that moment, so you can render the whole run from deliveries alone. The feed covers a search’s first 100 people, in the order the search found them, so a larger search announces those 100 and leaves the rest to a read of GET /v3/search/{search_id}. result.profile is present on every result that has a readable profile. A generating result is a person Candidate Discovery has found and started building, so it carries the row alone until its first profile exists. A failed result carries result.failure in place of a profile. candidate_discovery_completed appears on searches that ran with candidate_discovery: true.
A result is announced whenever its status or generation_level changes, which is what a poll loop watches for: Key your store by result.profile_id and replace the row on each delivery, the same way you would update a polled results entry in place. occurred_at orders deliveries for one search.

search.completed

Sent once per search on the feed, when it reaches completed, completed_with_errors, or failed. It closes out the run: the profiles arrived in their own search.profile.updated deliveries, so the roster here names each result and its final state.
Treat this delivery the way you treat a poll that returns a terminal status: the search is finished and needs no further reads. result_count, ready_count and failed_count describe the whole search. results lists the people the feed covered — its first 100, in the order the search found them. A larger search also carries results_truncated: true and results_limit: 100, and stays fully readable through GET /v3/search/{search_id}. The same 100 people are the ones search.profile.updated announces.

Other Events

company.thesis.changed, company.alert, and portfolio.changed are delivered to endpoints subscribed to them and follow the same signing scheme. Their payloads are documented with the company intelligence APIs.