Skip to main content

Webhook subscription lifecycle

A webhook subscription is one row — a (destination URL, events filter) pair — that moves through a lifecycle: registered → active → (on sustained failure or proven-dead response) disabled → re-enabled. This page walks that loop from the operator’s seat: what happens at each state, where to watch it, and the exact re-enable flow when a subscription has been taken out. Fill-in details that live on sibling pages are linked, not repeated — per-attempt wire semantics sit in Webhook delivery semantics and the one-event-to-many-destinations topology sits in Webhook fan-out and event sinks.

1. Register a subscription — scope, events, destination

A subscription is created either in the dashboard (Settings → Webhooks → Add endpoint) or by API (POST /api/v1/webhooks). Three fields set its contract:
  • url — the HTTPS destination Orbit POSTs every matching event to. Production endpoints require HTTPS, and the URL is validated to block requests to internal addresses.
  • events — the filter for what this subscription receives: exact types (message.delivered), prefix globs (message.*), or * for everything. The filter is tenant-scoped and evaluated at fan-out time — a subscription never sees a non-matching event.
  • secret — your signing secret. Omit it and Orbit auto-generates one; the response returns the whsec_... cleartext exactly once.
Scope is one subscription per tenant per URL — nothing is shared across tenants, and a subscription is independent of every other: endpoint A’s bad day never slows or blocks endpoint B (the reason is the per-subscription fan-out topology in Webhook fan-out and event sinks). An optional description, custom headers, a per-endpoint timeout_seconds (1–30s), a transform config, or an agent scope can pin the subscription further — all are partial-update fields on PATCH /api/v1/webhooks/{id}.

2. Signing — the secret and its rotation

Every delivery is signed HMAC-SHA256 with your subscription’s secret; the signature lands in X-Orbit-Signature (canonical) as t=<unix>,v1=<hex> over <t>.<raw_body>. Verify before you process — a failed check must drop the delivery before your handler runs, or spoofed payloads pollute your dedup set. Copy-paste verifiers for Node.js, Python, and Go: Webhook security. Rotation is a first-class lifecycle event, not a recreate. When you rotate the secret, Orbit opens a dual-signing grace window: new deliveries carry the new-secret signature and the previous-secret signature side-by-side (X-Orbit-Signature-Next), so your verifier keeps validating while you deploy the new key. The API exposes the full rotation loop as first-class calls — POST /api/v1/webhooks/{id}/rotate-secret, /rotation/initiate, /rotation/complete, and /rotation/cancel — so you can script it. Body-hash verification logic, the legacy X-Devotel-Signature back-compat header, and common verification failures are covered in Webhook security and Troubleshooting signature failures.

3. Delivery attempt grid — initial attempt vs. retry ladder

Each event against your subscription is delivered on a fixed grid: one initial attempt plus 9 retries, 10 attempts total, on an exponential backoff starting at 30 seconds and doubling each round with +0–20% jitter. Any 2xx within 30 seconds (or within your per-endpoint timeout_seconds, if set below the platform 30s ceiling) is a success; everything else schedules a retry. If all 10 attempts fail, the event moves to your subscription’s dead-letter queue roughly 4.3 hours after the first attempt at nominal delays. A failed-delivery timeline (endpoint returning 503 every attempt, no jitter): The per-attempt contract — ordering, idempotency windows on both directions, and the two re-delivery causes — is the full subject of Webhook delivery semantics; this page deliberately does not restate it.

4. Failure classification — what each response means

Orbit classifies your endpoint’s response into buckets, and each bucket drives a different next step: 410 Gone doubles as the deliberate single-event skip: return it for an event type you have deprecated and you route that event straight to the dead-letter queue without retrying the ladder. Reserved response codes that mean “we will never succeed here” (401/403/404/410) are documented with the retry contract in Webhook delivery semantics.

5. Circuit-breaker: when Orbit stops delivering

Orbit runs a per-subscription circuit breaker over consecutive failures. Two triggers stop delivery:
  • Proven-dead response — the first 401 / 403 / 404 / 410 fires instantly (one failure, not a streak).
  • ~50 consecutive retryable failures — a sustained failure streak on otherwise-ordinary responses.
When the breaker trips, the subscription is auto-disabled: org admins are emailed, and subsequent matching events go straight to the subscription’s dead-letter queue — nothing is retried in the background, and nothing is lost. The breaker state is per subscription; other subscriptions are untouched. A disable is not delete. The endpoint row, its events filter, its signing secret, and its delivery history all remain — you are preserving the configuration to re-enable, not re-registering. (Contrast with PATCH /api/v1/webhooks/{id} with active: false, an explicit operator-driven deactivation that uses the same neutral “no deliveries” state but is your decision rather than Orbit’s.)

6. Re-enable: the operator loop

The fix → confirm → re-enable loop is three steps. Never skip step 1 — a re-enabled subscription that immediately fails again starts a new consecutive-failure streak.
  1. Fix the root cause on your side. Confirm your receiver returns 2xx within the timeout for a representative event; check TLS validity if you suspect it; fix auth if the disable was a proven-dead response.
  2. Probe with a test fire. Fire a synthetic event against the subscription and confirm a clean 2xx round-trip: POST /api/v1/webhooks/{id}/test (or /test-fire) — no production traffic yet.
  3. Re-enable the subscription. In the dashboard (Settings → Webhooks) toggle the endpoint back to active, or call PATCH /api/v1/webhooks/{id} with active: true:
The subscription transitions back to active and new matching events resume delivery immediately. After re-enable, drain the dead-letter backlog that accumulated while the subscription was disabled — requeue per delivery, or bulk:
A requeued delivery re-enters the retry pipeline as a fresh attempt — which is why your receiver’s dedup buffer must span the full 7-day dead-letter replay window, not just the initial retry schedule (see Webhook delivery semantics, section 1). The dashboard’s Developer → Webhooks → Dead-letter queue has one-click parity with both calls.

7. Event catalogue discovery

You do not subscribe to a fixed enumerated list — you discover the catalogue. Three surfaces carry it depending on what you are doing:
  • While picking events in the dashboard — the event picker on Settings → Webhooks → Add endpoint enumerates families (message.*, call.*, flow.execution.*, verification.*, email.*, and the rest) with names.
  • While integratingGET /api/v1/webhooks/events returns the canonical event catalogue programmatically, the same source the picker reads.
  • While reading docsWebhook events is the rendered catalogue, with payload shapes per event and per-family grouping; the events reference holds it as a flat list.
Subscribing * receives everything; a prefix glob (message.*) receives the family; an exact type receives only that type. Subscribing a name not in the catalogue returns 422 — typo’d names cannot silently persist.

Where this fits

This page is the operator loop for ONE subscription; two sibling concept pages carry the model one level down and one level up: