Skip to main content

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 be https, 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, default POST): one of GET, POST, PUT, PATCH, DELETE. A method outside the allowlist is rejected at save.
Validation runs twice, and the two layers are not the same check:
  1. Save time. The URL string is validated when the journey definition is saved, so an obviously unsafe URL never persists.
  2. 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.
If the send-time validation rejects the URL, the call for that contact is skipped and the journey moves on. Your endpoint simply never sees the request; Orbit logs the rejection server-side. Design URLs that stay public and resolvable for the life of the journey. One practical note on method: the payload travels as a JSON request body (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:
Field by field: Two contract details worth coding against:
  • journey_name can be empty. A journey whose campaign has neither a name nor a title sends "". Never branch on journey_name alone; pair it with campaign_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_id from 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 on journey_name and node_id. The example below verifies the timestamp, dedupes, responds 2xx immediately, and does the real work off the request path:
The load-bearing choices in that example are the ones every consumer should make:
  • 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.
For richer examples of payload-driven consumers, see Webhook payload cookbook.

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 http rather than https, 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 still https.
  • 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 new timestamp. 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:
  1. 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.
  2. 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.
  3. 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).
  4. 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