Wire delivery-report webhooks per channel, end to end
You send on SMS, WhatsApp, RCS, email, or voice and you want delivery outcomes pushed to your server instead of polling the Delivery Log. This tutorial closes the loop in one pass: pick the right events per channel, subscribe, verify signatures, rehearse in the sandbox before registering, and debug a misbehaving delivery by request id. Pages that overlap this topic each answer a narrower question — the event catalog is for browsing payload schemas and pinning them in CI, the durable consumer guide is endpoint hardening generally, and DLR outcomes monitoring is the WARN-log classification readout. This page is the wiring tutorial that connects them.1. Event vocabulary per channel
Four message events carry the outbound lifecycle on every messaging channel, plus the call family for voice:
Subscribe to all four message events even if you only care about
failures:
message.sent is how you distinguish “Orbit never accepted the
send” from “the channel lost it downstream,” and message.delivered
lets you reconcile that message.failed genuinely means terminal.
The changed semantics you must code against
The event names are stable; what each means per channel is not. These are the rows that change how you wire:- Meta DM (Messenger, Instagram): no DLR exists. Meta’s Send API
never emits delivery receipts, so a DM send can only produce
message.sentfollowed by either nothing or a safety-netmessage.failedreadingsubmitted_no_receiptafter a 5-minute window. Treatmessage.sentas fully accepted on these channels and do not alert on the missingmessage.delivered— it will never come. See DLR outcomes monitoring for the designed-benign classification. - Viber and WhatsApp: a 24-hour DLR window. A send that receives no
receipt inside 24 hours ages to
undeliveredand firesmessage.failed— a late genuine receipt still corrects the row and re-firesmessage.delivered(ormessage.read). Your consumer must accept a correcting event after a terminal one. - SMS on SMPP-backed routes: a 30-minute window. A healthy carrier
returns receipts well inside 30 minutes. A send with no receipt inside
that window ages to
undeliveredand firesmessage.failed; a late receipt corrects it. A sustained no-receipt rate on this lane is a carrier problem, not a platform problem. - Email semantics are mailbox-acceptance.
message.deliveredmeans the receiving server accepted the message — it is not an open or a click. Pair it with the engagement events (email.opened,email.clicked) if you need recipient interaction. - Voice has no delivery concept.
call.answeredis the voice equivalent of delivered;call.failedcovers busy, no-answer, and carrier failure. Subscribe to the full call lifecycle instead of a single event.
id, and key each event to the message by
data.message_id — never by arrival order.
2. Subscribe
In the dashboard
Open Developer → Webhooks, click Add endpoint, paste your HTTPS URL, and tick the events from the table above. The dashboard shows the signing secret once — copy it now; it is the valuewhsec_... your
verifier needs in Step 3.
Over the API
POST /api/v1/webhooks/endpoints with the event list per channel you
just mapped:
422 VALIDATION_ERROR — resolve event names against the events
catalog first. One endpoint can serve several
channels; the data.channel field on every message event tells you
which channel the receipt came from, so a single subscription covers
SMS, WhatsApp, RCS, email, and voice if your handler branches on
data.channel.
3. Verify the signature
Every registered delivery carriesX-Orbit-Signature: t=<unix_ts>,v1=<hex>
— an HMAC-SHA256 of <t>.<raw_body> under your endpoint secret. Verify
before parsing, answer 401 on failure, and never ack an unverified
payload with 200. The full protocol lives at Webhook
security.
Node.js (standard library)
Python (standard library)
X-Orbit-Signature (current secret) and X-Orbit-Signature-Next
(previous secret) arrive until the 7-day grace window ends.
4. Sandbox rehearsal — tester first, then a real subscription
Do not register a production endpoint against an untested receiver. Two rehearsal steps, both without a carrier: Step A — fire the Webhook Tester. Open Developer → Webhook Tester, paste your URL, pickmessage.delivered, and send. The
tester delivers through the real dispatcher but unsigned — a
receiver that verifies correctly answers 401/403 to it. Send tester
events to confirm your route exists, your TLS is valid, and your
handler parses the envelope; expect signature verification to reject
them, per the Webhook Tester checklist.
Step B — register with a test key and replay a delivery. Mint a
sandbox key (dv_test_sk_), register the endpoint over the API with
that key, and drive a magic-number send. The trailing digit picks the
outcome; trailing digit 3 returns message.failed (the stored status
is undelivered, a handset reject):
message.sent immediately, then message.failed
about a second later — the same sequence a carrier produces on a
handset reject. Verify the signature against the test-endpoint secret,
confirm your failure branch ran, then walk the other digits (2
delivered, 4 pre-submit failure with no sent ack, 9 delayed
delivery) before you swap in a live key. The full digit table is the
sandbox magic numbers playbook.
5. Debug a delivery
When an expected event never lands, or lands and your handler rejects it, correlate on two handles:meta.request_idon the API response. Every send response carries it. Open Developer → Request Logs, find the request by id, and confirm Orbit accepted the send — a422or502at the request layer means no DLR events will ever fire.data.message_id(anddata.to, the recipient id) on the event envelope. Open the Delivery Log filtered by the message id. The row’s terminal status is the ground truth; the webhook event you expected is downstream of the same transition the row shows. Check Developer → Webhooks → your endpoint → Deliveries for the dispatch record — a failing endpoint shows its failed attempts and the retry timeline there.
Compliance
Everything on this page is a tenant-owned control: which events you subscribe to, which URL receives them, and how your consumer reacts to each outcome. The channel windows (Meta DM no-DLR, the Viber/WhatsApp 24-hour window, the SMPP 30-minute window) are platform safety nets you read, not timers you set. Outbound delivery stays within the Devotel softswitch contract described in DLR and MO gateway pipeline — subscribing to DLR events sends no traffic anywhere new.Related
- Webhook events catalog — the canonical list of every event you can subscribe to
- Build a durable webhook consumer — queue, dedupe, and ack-fast hardening for the receiver this page wires
- Verify webhook signatures — one-call SDK verifiers per language
- Webhook Tester — the unsigned ad-hoc sender used in Step A
- DLR outcomes monitoring — the WARN classification behind the no-receipt terminal events
- Sandbox magic numbers playbook — the full trailing-digit scenario table
- Delivery lifecycle — what each of the six canonical states means