Skip to main content

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:
  1. The destination number is not in an active, pending_compliance, or suspended state — e.g. released after a teardown, or nothing owned the number at the time of arrival.
  2. The number’s routing index entry is missing or points at a tenant that has since been offboarded (deleted).
Check the Numbers page: if the number reads 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 to disabled. 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:
  1. 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.
  2. Check for the receiver faults that break inbound deliveries. Orbit accepts a delivery on any 2xx within 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 /webhooks while the endpoint is registered as /webhooks/inbound-sms answers 404 — again proven-dead.
    • Slow processing. A handler that blocks on downstream work past the 30-second window times out. Acknowledge with a quick 200 and process asynchronously instead.
  3. Confirm the response under the retry posture. Any 2xx inside the 30-second window is accepted; timeouts and 5xx responses 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/410 skip 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.
  4. Fix the receiver, then replay. Once the endpoint answers 2xx again, 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 200 once 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 2xx here, 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, or suspended) has not recorded inbound for more than 2 hours and you have already ruled out a disabled per-DID override.
  • You see the “optional” route configuration panel set the sms_route_type for that DID to disabled — clear the override first, then confirm inbound resolves again.
  • All inbound stops across many unrelated numbers and your webhook endpoint is returning 2xx on the Events timeline (a control-plane symptom, not an index one).
To let support trace the gap without a back-and-forth, capture:
  • The affected E.164 number (+12345678900) from the Numbers page.
  • Your tenant ID (dashboard → Settings → Organization; or GET /api/v1/me as organizationId).
  • A timestamp of one inbound attempt that did not record.

See also