> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Normalized inbound event envelope

> One carrier-agnostic envelope for inbound messaging webhooks — the data.normalized block, the normalized.inbound share subscription, and the inbound-events catalog endpoint.

# Normalized inbound event envelope

Inbound messaging events (received messages, delivery receipts, failures, reads, opt-outs) come from several upstream carriers, and each carrier reports a different status vocabulary: Telnyx fires `message.sending_failed`, SMPP receipts carry codes like `UNDLV` and `DELIVRD`, DIDWW reports its own status strings. Orbit folds all of them onto the same canonical webhook events — this page documents the three pieces that make that folding predictable:

1. The **`data.normalized` block** stamped on inbound message and opt-out envelopes
2. The **`normalized.inbound` share event** — one subscription covering the whole inbound family
3. The **inbound-events catalog endpoint** — the machine-readable mapping table of every upstream carrier status to the canonical event it resolves to

Your existing subscriptions and signature verification are unchanged. All three are additive: receivers that ignore them behave exactly as before.

## The `data.normalized` block

Deliveries whose event belongs to the inbound family carry an extra `normalized` object inside `data`:

```json theme={null}
{
  "id": "evt_n0rm41l1z3d",
  "type": "message.delivered",
  "created_at": "2026-08-26T12:00:00Z",
  "data": {
    "message_id": "msg_abc123",
    "channel": "sms",
    "status": "delivered",
    "timestamp": "2026-08-26T12:00:00Z",
    "normalized": {
      "event": "message.delivered",
      "kind": "message",
      "data": { "message_id": "msg_abc123", "status": "delivered" }
    }
  }
}
```

| Field              | Values                                        | Notes                                                                      |
| ------------------ | --------------------------------------------- | -------------------------------------------------------------------------- |
| `normalized.event` | An event from the [catalog](/webhooks/events) | The concrete event type of the delivery — equals the envelope `type`.      |
| `normalized.kind`  | `inbound`, `message`, `opt_out`               | A closed discriminator you can branch on instead of parsing event strings. |
| `normalized.data`  | object                                        | The full original payload, preserved verbatim.                             |

The `kind` discriminator:

| `kind`    | Event types                                           | Meaning                                 |
| --------- | ----------------------------------------------------- | --------------------------------------- |
| `inbound` | `message.received`                                    | A message arrived from the recipient.   |
| `message` | `message.delivered`, `message.failed`, `message.read` | A status update for a message you sent. |
| `opt_out` | `contact.opted_out`                                   | The recipient opted out.                |

The block is additive — every field your handler already reads keeps its existing name and position. Events outside the inbound family do not carry `normalized`.

## Subscribe once with `normalized.inbound`

`normalized.inbound` is a share-able subscription name that covers the whole inbound family at once. Set it as an endpoint's entire event list and the endpoint receives `message.received`, `message.delivered`, `message.failed`, `message.read`, and `contact.opted_out` — without enumerating them:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/webhooks" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/orbit-inbound",
    "events": ["normalized.inbound"]
  }'
```

Endpoints that subscribe to the individual event names (or to `*`) behave exactly as before — the share name is a convenience, not a replacement.

## The inbound-events catalog

The mapping table itself is served by one authed endpoint:

<Note>
  `GET /api/v1/webhooks/inbound-events`
</Note>

The response lists one entry per canonical event a receiver can observe. Each entry carries the canonical status, the high-level `kind`, and the full list of upstream carrier statuses (grouped by carrier — the Devotel softswitch, Telnyx, DIDWW, and Meta) that normalize to it.

```bash theme={null}
curl -X GET "https://api.orbit.devotel.io/api/v1/webhooks/inbound-events" \
  -H "X-API-Key: dv_live_sk_..."
```

```json theme={null}
{
  "version": "v1",
  "events": [
    {
      "canonical_event": "message.delivered",
      "canonical_status": "delivered",
      "kind": "delivery",
      "equivalent_upstream": [
        { "provider": "telnyx", "raw_status": "message.delivered" },
        { "provider": "devotel_softswitch", "raw_status": "delivrd" },
        { "provider": "didww", "raw_status": "delivered" },
        { "provider": "meta", "raw_status": "delivered" }
      ],
      "normalizer": "normalizeInboundEvent"
    }
  ]
}
```

The `kind` values here (`delivery`, `failure`, `opt_out`, `received`) describe the carrier-status mapping; the `kind` values on the `data.normalized` envelope block above (`inbound`, `message`, `opt_out`) describe the delivery's place in the inbound family. Both are closed enums you can branch on.

Use the catalog to check that your handler's switch statement covers the same mapping the platform normalizes with — for example when adding support for a new carrier, or when a raw status appears in your logs that your switch does not yet handle. The same table ships in the SDK as `normalizeInboundEvent` in the shared webhook normalization module: 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.

## See also

* [Webhook Events catalog](/webhooks/events) — every event type you can subscribe to
* [Webhook Event Payloads](/webhooks/event-payloads) — the full envelope and header anatomy
* [Webhook Overview](/webhooks/overview) — endpoints, signing, retries, and the dead-letter queue
* [Webhook Security](/webhooks/security) — signature verification
