Troubleshooting: inbound SMS not reaching your webhooks
Inbound SMS (mobile-originated messages, or carrier delivery receipts) should land in your Inbox and fire your message webhooks the moment the carrier hands the traffic to Orbit. If one of your numbers receives texts but nothing is recorded — no Inbox row, no webhook payload — work the checks below in order.Check the routing first
Orbit resolves every inbound message to a tenant by looking the destination number up in the platform’s number index, then falling back to a bounded scan of tenant records. A message can only be dropped in this path when:- The destination number is not in an
active,pending_compliance, orsuspendedstate — e.g.releasedafter a teardown, or nothing owned the number at the time of arrival. - The number’s routing index entry is missing or points at a tenant that has since been offboarded (deleted).
released or was never purchased, routing correctly did not resolve. If the number reads one of the three routed states but inbound still never lands, the index entry is the suspect.
The self-healing index
Orbit runs an hourly background reconciliation that scans every live tenant’s SMS-capable numbers and re-creates any routing index entries that drifted — for example after a purchase-time write failed silently, or after an out-of-band administrative edit bypassed the normal write path. Inbound routing correctness is not subscription-gated: a tenant whose subscription has cancelled keeps its SMS-capable numbers reconcilable. Because reconciliation runs hourly, a one-time drift usually self-clears within one hour. If inbound was missing and spontaneously returns after the next hourly window, that is the expected self-heal path — no support action needed.Work by observation pattern
- One specific number never gets inbound, while other tenants’ numbers do. If the number is in a routed state and the gap persists beyond a couple of hours, the hourly reconciliation should already have repaired it — open a support ticket; this class is the customer-facing symptom that should never survive the hourly sweep.
- Only DLRs are missing but MO arrives fine. DLRs follow the same resolution path as MO messages; a missing-receipt DLR is always a carrier late-arrival problem first — see the delivery-receipt troubleshooting page.
The webhook receiver: when most inbound stops at once
When inbound stops across many numbers at the same time, the per-number index is rarely the cause — the usual suspect is the webhook endpoint those events deliver into, or a dashboard-per-DID route override set todisabled.
Inbound MO and DLR events deliver as webhook events (message.received for MO; message.delivered / message.failed for receipts), so the receiver check applies to both. Work these in order:
- Look at the delivery attempts. Open Developer → Webhooks → Events in the dashboard and filter to your endpoint and
status=failed. Each failed row records the exact status, response body, or timeout note your server returned — Orbit records what it received, so there is no guesswork about your endpoint’s answer. The same data is on the API (GET /api/v1/webhooks/events?status=failed). Walkthrough: Inspect webhook deliveries and replay failures. - Check for the receiver faults that break inbound deliveries. Orbit accepts a delivery on any
2xxwithin a 30-second window; anything else retries or fails:- Auth middleware rejecting the requests. A receiver that verifies a cookie/bearer token it expects from browser sessions, or that uses the wrong signing secret, answers
401/403. Either lies in the proven-dead set (401,403,404,410) — responses in that set skip retries entirely, land in the dead-letter queue, and disable the endpoint. - No response returned. A handler that processes the message but never
returns a response before the connection closes counts as a timeout or an empty-response failure. - Wrong URL path on the endpoint registration. A listener that serves
/webhookswhile the endpoint is registered as/webhooks/inbound-smsanswers404— again proven-dead. - Slow processing. A handler that blocks on downstream work past the 30-second window times out. Acknowledge with a quick
200and process asynchronously instead.
- Auth middleware rejecting the requests. A receiver that verifies a cookie/bearer token it expects from browser sessions, or that uses the wrong signing secret, answers
- Confirm the response under the retry posture. Any
2xxinside the 30-second window is accepted; timeouts and5xxresponses retry on a doubling backoff (30 seconds, 60, 120, up to ~4.3 hours of window across 10 attempts) before the event moves to the dead-letter queue, replayable for 7 days.401/403/404/410skip retries and disable the endpoint; 50 consecutive failures of any kind auto-disable it too — deliveries to a disabled endpoint stop arriving entirely. Full schedule: Webhooks overview — Retry Schedule and Dead-Letter Queue. - Fix the receiver, then replay. Once the endpoint answers
2xxagain, put the missed deliveries back from Developer → Webhooks → Events (or the dead-letter queue page) — see Inspecting deliveries. Always test this path with a real delivery, not a synthetic unsigned request: an unsigned probe only exercises your auth middleware, not your receiver logic.
Acks the receiver returns
Two different HTTP responses show up in this incident and they are easy to confuse:- Orbit’s reply to the carrier: Orbit always answers the carrier’s inbound POST with
200once it has resolved the message — a carrier-side delivery problem is not implicated by that ack. - Your receiver’s reply to Orbit: the status the Events timeline and its error column report — this is the ack the checklist above is about. Only a
2xxhere, inside the window, counts as accepted.
When to escalate
Open a support ticket when one of these holds:- A number in a routed state (
active,pending_compliance, orsuspended) has not recorded inbound for more than 2 hours and you have already ruled out adisabledper-DID override. - You see the “optional” route configuration panel set the
sms_route_typefor that DID todisabled— clear the override first, then confirm inbound resolves again. - All inbound stops across many unrelated numbers and your webhook endpoint is returning
2xxon the Events timeline (a control-plane symptom, not an index one).
- The affected E.164 number (
+12345678900) from the Numbers page. - Your tenant ID (dashboard → Settings → Organization; or
GET /api/v1/measorganizationId). - A timestamp of one inbound attempt that did not record.
See also
- Inbound message resolution — the concept page for resolution paths, routed states, reconcile, and the per-DID override this flow checks
- Webhooks overview — the receiver-side reference: delivery envelope, retries, proven-dead statuses, and the dead-letter queue this page’s receiver checklist cites
- Inspect webhook deliveries and replay failures — the delivery log and replay tooling referenced in the receiver checklist
- Delivery receipts lifecycle — the post-queue receipt ladder and grace windows before you suspect an index gap
- Channels → SMS — per-channel capability notes
- Numbers → Lifecycle — what each routed state means and how releases/teardowns interact with routing