Skip to main content

Inbound event catalog

Inbound message statuses (delivered, failed, read, received, opt-out) come from every carrier Orbit integrates — Telnyx, the Devotel softswitch, DIDWW, and Meta — and each carrier reports its own status strings. Orbit folds all of them onto the same canonical webhook events. The Inbound event catalog is the dashboard view of that folding: one row per canonical event, listing every raw upstream status that arrives as it. Use it before you write a receiver branch — the catalog answers “which canonical event do I subscribe to, and which raw provider statuses will arrive as it?”

Open the catalog

In the dashboard, go to Developer → Webhooks → Inbound event catalog (/developer/webhooks/inbound-catalog). The page is available to members with the owner, admin, or developer role. The catalog is static platform metadata with no tenant data, so every member with one of those roles sees the same table.

The catalog endpoint

The same table is served machine-readably:
GET /api/v1/webhooks/inbound-events
The response lists one entry per canonical event a receiver can observe, each with the canonical status, the high-level kind, and the full list of upstream carrier statuses (grouped by carrier) that normalize to it. See Normalized inbound event envelope for the response shape.

Read an entry

The page has two panes: a searchable list of canonical events on the left, and the selected event’s detail on the right. Each entry shows: The list’s search matches canonical event names, statuses, kinds, and provider vocabularies, so typing delivrd finds the same entry as typing delivered.

A folding entry you can read end-to-end

Take the SMPP failure code UNDLV and follow it from the carrier’s receipt to the webhook your endpoint receives. The carrier reports a delivery receipt with the raw status UNDLV (the SMPP delivery-failure code the Devotel softswitch reports). Orbit folds it into the canonical failure pair: The signed envelope your receiver gets then reads:
The carrier’s own status always survives the fold: data.provider_status echoes the status exactly as it arrived ("UNDLV" here), while data.status stays a closed canonical set (delivered, failed, sent, read, opted_out) you can switch on. Branch on the canonical pair; log the raw string. The same two-step fold works for every carrier — Telnyx message.delivered lands as message.delivered with data.status of delivered and data.provider_status of message.delivered; a Meta read status lands as message.read; a DIDWW undelivered lands as message.failed.

Which carriers report which vocabulary

The catalog covers four upstream providers. Their status families: Every left-column term arrives at your endpoint as an equivalent (provider, raw status) pair on the catalog entry for the canonical event it folds into. Matching is case-insensitive: a UNDLV receipt and an undlv catalog row are the same entry.

When to use normalized.inbound vs subscribing per event

Two subscription styles cover the inbound family:
  • One share subscription — set the endpoint’s event list to ["normalized.inbound"] and it receives the whole family (message.received, message.delivered, message.failed, message.read, contact.opted_out) without enumerating events. Choose this when your handler keys off data.normalized.kind (delivery / failure / opt_out / received) and you never want to miss a status family.
  • Per-event subscriptions — list the specific events (["message.delivered", "message.failed"]). Choose this when separate teams or pipelines own separate outcomes — for example a deliverability dashboard subscribed to message.failed only — or when a success pipeline should not have to filter out opt-outs.
Both styles receive identical envelopes; the trade-off is only how you split routing. Example endpoint with the share subscription:
See Normalized inbound event envelope for the full data.normalized block anatomy.

Receiver validation with the SDK

The same mapping the platform normalizes with ships in the SDK as normalizeInboundEvent, so your receiver validates against the identical table instead of hand-rolled per-carrier parsing. Pass a raw carrier status (the provider name is optional) and it returns the canonical pair:
The call is case-insensitive and whitespace-trimmed, and returns undefined for a raw status the catalog does not cover — treat undefined as “log it and keep data.status”, never as a silent drop. Omit provider and every provider’s vocabulary is searched, so a carrier-agnostic receiver validates with one import.

Reading provider_status in a handler

On the delivery envelope, two fields carry status: the canonical data.status and the carrier-verbatim data.provider_status. Switch on the first; keep the second for per-carrier detail:
Because the raw term survives the fold, you keep the carrier’s nuance where you need it and a single closed switch where you don’t.

Put it to work

Use the catalog when your handler sees a raw status that does not match any branch:
  1. Search the raw status (for example UNDLV) in the catalog.
  2. Read off the canonical event it resolves to (for example message.failed) and the canonical data.status (for example failed).
  3. Check that your handler’s switch covers that canonical event — SMPP UNDLV-style codes, Telnyx failure strings, and DIDWW statuses can all fold into the same event.
The same table ships in the SDK as normalizeInboundEvent: pass it a raw carrier status (with or without the carrier name) and it returns the canonical event and status pair, so receiver-side validation never drifts from the platform’s mapping. One subscription can skip the enumeration entirely: set an endpoint’s events to normalized.inbound and it receives the whole inbound family (message.received, message.delivered, message.failed, message.read, and contact.opted_out). See Normalized inbound event envelope.

Common misreads to avoid

The same misunderstanding recurs in receivers: a carrier reports one thing, Orbit normalizes to another. These are the folds that catch people:
  • “The carrier said delivered, but Orbit reports failed.” Normalization collapses carrier nuance into a closed set. An SMPP UNDLV receipt and a DIDWW undelivered both land as message.failed with data.status of failed — the original string survives in data.provider_status, so a handler that greps the raw string and ignores the canonical field misbranches. Conversely, Telnyx queued and sending fold to message.sent (kind delivery), not delivered: only message.delivered, delivered, or delivery_success mean delivered.
  • Treating read as a delivery outcome. Meta read is its own canonical event (message.read); subscribe to it explicitly or it looks like a missing message.delivered branch.
  • Expecting one canonical event per provider. The event list on an endpoint says which canonical events to receive; the provider that produced them is data on the envelope (data.normalized.provider), not part of the event name. SMPP UNDLV, Telnyx rejected, and DIDWW undelivered all arrive as message.failed.
  • Assuming unknown statuses are dropped. A raw status the catalog does not cover returns undefined from normalizeInboundEvent; the platform logs it and keeps the carrier-verbatim data.provider_status on the delivery — it is never silently swallowed.

When you want the schema instead

The Inbound event catalog answers “which raw status arrives under which event” — a status-folding view. For the full schema of what an endpoint can subscribe to (every event type, with sample payloads and failure payload shapes), use the schema-level Event catalog at Developer → Webhooks → Event catalog (/developer/webhooks/event-catalog).

See also