Consume journey webhook node calls
The journey builder’s Webhook node calls an external HTTPS endpoint each time a contact traverses that node in a journey graph. It is the way to hand a per-contact hand-off to your own systems mid-journey: sync the contact to a CRM when they reach a stage, notify an order system when a win-back journey re-qualifies them, or fan a journey event out to an internal bus. This guide is for the engineer on the receiving end. It covers what Orbit sends, what it guarantees, what it deliberately does not guarantee, and how to build an endpoint that behaves correctly when a delivery fails, times out, or arrives twice. For building the journey itself, see Build, simulate, and launch a campaign journey. For the hardening checklist every production webhook consumer should pass, see Webhook consumer production checklist.1. Journey webhook node vs. organization event webhook
Orbit has two ways to push data to your endpoint, and they are not interchangeable:
Pick the journey node when the fact that matters is “this contact reached this step of this journey.” The call is journey-scoped and per-contact, and it sits inside the graph, so it participates in branches and waits: put the node after a condition and only contacts who passed the condition trigger the call.
Pick an organization event webhook when your system needs the event regardless of any journey, or when you need the broader event catalog (message statuses, conversation events, and the rest of the catalog documented in Webhook event catalog). Organization webhooks fire whether or not a journey is involved; the journey node fires only for contacts actually walking the graph.
2. Configuring the node
In the journey canvas, the Webhook node takes two settings:webhookUrl(required): the HTTPS endpoint to call. The URL is validated when the journey is saved: it must behttps, and URLs that point at localhost, private ranges, link-local ranges, or cloud-metadata addresses are rejected with a validation error naming the node.method(optional, defaultPOST): one ofGET,POST,PUT,PATCH,DELETE. A method outside the allowlist is rejected at save.
- Save time. The URL string is validated when the journey definition is saved, so an obviously unsafe URL never persists.
- Send time. When a contact reaches the node, the URL is validated again, this time against the addresses the hostname actually resolves to, immediately before the call is made. This closes the gap where a hostname that was public at save time later resolves to an internal address.
Content-Type: application/json). POST, PUT, PATCH, and DELETE can carry that body. A GET request cannot, so a node configured with method: "GET" cannot deliver the payload; the call is recorded as failed server-side and skipped. Leave the default POST unless your endpoint explicitly expects PUT, PATCH, or DELETE.
3. The payload contract
Every delivery sends exactly this JSON body:
Two contract details worth coding against:
journey_namecan be empty. A journey whose campaign has neither a name nor a title sends"". Never branch onjourney_namealone; pair it withcampaign_id, or treat the empty string as an explicit case.- The shape is fixed. The six fields above are the whole payload. If you need more contact context, fetch it by
contact_idfrom the API rather than expecting extra fields to appear.
4. Worked consumer: one endpoint, three journeys
A common topology is a single endpoint serving every journey in the organization, branching onjourney_name and node_id. The example below verifies the timestamp, dedupes, responds 2xx immediately, and does the real work off the request path:
- Respond 2xx inside the 10-second budget (below) and queue the real work. Any processing you do before responding counts against Orbit’s timeout.
- Deduplicate on
(campaign_id, node_id, contact_id)because re-traversal is legal. - Branch defensively: an endpoint that serves several journeys will eventually receive a journey it does not know about yet.
- Verify the timestamp window so a replayed or very old delivery cannot re-trigger processing.
5. Semantics you must plan for
The node is fire-and-forget by design. Plan for each of these:- No retries and no dead-letter queue. If the call fails, times out, or is skipped, Orbit logs it server-side and the contact continues down the journey as if the call succeeded. There is no second attempt and no queue to drain later. If a hand-off is business-critical, make your endpoint durable on the first attempt (accept, persist, process asynchronously) and monitor your own inbound logs for gaps.
- A 10-second response budget. Each call carries a 10-second timeout. An endpoint that responds in 10.001 seconds is a failed call, identical to a 5xx from the node’s point of view.
- Pausing aborts in-flight calls. Pausing the campaign aborts webhook calls that are in flight at that moment. If you were mid-burst when an operator paused the journey, expect some contacts’ calls to be aborted rather than delivered; the journey resumes from where it paused, and those contacts continue (their aborted calls are not retried).
- SSRF-rejected URLs are skipped silently from your side. A URL that fails the send-time safety validation, is
httprather thanhttps, or resolves to a private, link-local, or metadata address is never dialed. Your endpoint sees nothing; the skip is visible only in Orbit’s server-side logs. If deliveries for one journey stop entirely while other journeys keep arriving, check that the journey’s URL is still public and stillhttps. - Duplicates are normal. A contact that re-enters a node, after a wait or a loop, produces another delivery with the same
(campaign_id, node_id, contact_id)and a newtimestamp. Design for at-least-once arrival with consumer-side dedup, not exactly-once delivery. - Journey webhook calls are not signed. The payload carries no signature header. If you need to authenticate the sender, gate at the network layer (allowlist Orbit’s egress ranges, or put a shared secret in the URL you configure on the node), or use organization event webhooks, which support signature verification, for anything that must be provably from Orbit. See Verifying webhook signatures.
6. Testing loop before launch
Test the pairing before any contact walks the graph live:- Dry-run the journey in the simulator first. The simulator walks test contacts through the graph and shows per-node behavior, including webhook nodes, before launch. See Campaign journey simulation and inspection.
- Point the node at the webhook tester or a sandbox sink. Capture the exact body your endpoint will receive, and assert your parser accepts all six fields, including an empty
journey_name. See Webhook tester. - Enroll a single test contact in the sandbox and watch the delivery arrive end to end, including your dedup and timestamp-window paths (send the same contact through twice on purpose).
- Load-test your response time. Your p99 must sit comfortably inside the 10-second budget with headroom, because anything above it is a silently failed hand-off.
7. Troubleshooting
Related
- Build, simulate, and launch a campaign journey: the canvas, node vocabulary, and save-time validation errors.
- Webhook consumer production checklist: the general hardening list for any endpoint that receives Orbit webhooks.
- Webhook payload cookbook: payload-handling patterns across Orbit’s webhook surfaces.
- Campaign journey simulation and inspection: dry-run a journey, webhook nodes included, before launch.