# Message status (DLR) and inbound (MO) webhook payloads

> Two webhook payloads carry messaging events. A delivery-status (DLR) event reports a message's outcome — message_id, a status enum (accepted, delivered, undelivered, expired, rejected), an error reason and timestamps. An inbound (MO) event reports a message a subscriber sent to you. Both are the HTTP equivalents of SMPP deliver_sm receipts and mobile-originated messages.

- Canonical URL: https://www.broadnet.me/reference/message-status-and-inbound-webhooks
- Language: en
- Last updated: 2026-07-19
- Publisher: BroadNet (https://www.broadnet.me)

The payload schemas for the two core messaging callbacks: the delivery-status (DLR) event with its message id, status enum, error reason and timestamps, and the inbound (MO) message event — plus an event-type taxonomy and how an HTTP DLR maps onto an SMPP deliver_sm receipt.

## Two event families: status and inbound

A messaging platform emits two fundamentally different callbacks. A delivery-status event (DLR, delivery receipt) is the asynchronous follow-up to a message you sent: it tells you what finally happened to it in the operator network. An inbound event (MO, mobile-originated) is unsolicited — a message a subscriber sent to one of your numbers or a reply to a campaign. They share the webhook transport and signing, but their schemas and their meaning differ, so a robust consumer branches on an event-type discriminator before parsing the rest. Both are the HTTP-layer expression of things SMPP delivers as deliver_sm PDUs: a receipt flag distinguishes a DLR from an MO on the wire, and the same distinction is an explicit event field over HTTP.

## Delivery-status (DLR) payload

A delivery-status payload correlates to the original send by the server-assigned message_id and reports a status drawn from a small enumeration, plus the reason and the timestamps that let you measure latency and reconcile billing. The status enum is the heart of it: it distinguishes an intermediate accepted/enroute state from the terminal states, and separates a positive delivered from the several ways a message can fail. Terminal negative states carry an error code and human-readable reason so a consumer can decide whether the destination is permanently bad (suppress it) or the failure was transient (safe to resend). Timestamps typically include when the platform accepted the message and when the terminal status occurred.

*Delivery-status (DLR) webhook body*

```json
{
  "event": "message.delivered",
  "message_id": "01HZY8Q3M6",
  "to": "+15551234567",
  "status": "delivered",
  "error": null,
  "reason": null,
  "submitted_at": "2026-07-19T09:15:22Z",
  "done_at": "2026-07-19T09:15:25Z",
  "sequence": 3
}
```

## The delivery-status enumeration

Standardising the status values is what lets one consumer handle every operator, because raw network responses vary wildly. A canonical enum maps them to a handful of states with clear semantics: whether the state is terminal, and whether it is a success. Intermediate states (accepted, enroute) may be superseded by a later event for the same message id, which is exactly why payloads carry a sequence or timestamp and why consumers apply the self-ordering rule from webhook reliability — a late accepted must never overwrite an earlier delivered.

*Canonical delivery-status values*

| status | Terminal | Meaning |
|---|---|---|
| accepted | No | Platform accepted the message for delivery |
| enroute | No | Handed to the operator, awaiting a receipt |
| delivered | Yes | Confirmed delivered to the handset |
| undelivered | Yes | Operator could not deliver (see error/reason) |
| expired | Yes | Validity period elapsed before delivery |
| rejected | Yes | Refused (invalid number, blocked sender, spam filter) |
| unknown | Yes | Operator returned no definitive final state |

## Inbound (MO) message payload

An inbound event carries the message content plus the routing needed to reply and to correlate a conversation. The critical fields are from (the subscriber's MSISDN in E.164), to (the number or short/long code they messaged), and the text; alongside them a message id for the inbound message itself, a received timestamp, and — because inbound SMS is subject to the same encoding and segmentation rules as outbound — the encoding and, for a multipart message, the concatenation reference and part indices so the consumer can reassemble the full text. Inbound events are how keyword campaigns, two-way conversations and STOP/HELP compliance flows are driven.

*Inbound (MO) webhook body*

```json
{
  "event": "message.inbound",
  "message_id": "01HZY8R7T0",
  "from": "+15551234567",
  "to": "44160",
  "text": "STOP",
  "encoding": "GSM7",
  "received_at": "2026-07-19T09:20:11Z",
  "concat": { "ref": null, "part": 1, "total": 1 }
}
```

## Event-type taxonomy

Beyond DLR and MO, a platform emits lifecycle and account events, and a consistent naming scheme — a dotted resource.action string — lets a consumer subscribe to and route them cleanly. Delivery-status events are variations on message.<status>; the inbound event is message.inbound. Grouping by the resource prefix means a consumer can match message.* for everything about a message and reserve other prefixes for account or number events. Every event should also carry a stable event id (used as the signature nonce and the dedup key) and the type string, so the discriminator and the idempotency key are always present.

*Common messaging event types*

| Event type | Family | Fired when |
|---|---|---|
| message.accepted | Status (DLR) | Message accepted for delivery |
| message.delivered | Status (DLR) | Delivery to the handset confirmed |
| message.undelivered | Status (DLR) | Operator failed to deliver |
| message.expired | Status (DLR) | Validity period elapsed |
| message.rejected | Status (DLR) | Message refused upstream |
| message.inbound | Inbound (MO) | A subscriber sent you a message |

## Mapping HTTP DLR to SMPP deliver_sm

The HTTP delivery-status callback and the SMPP delivery receipt are the same information in two encodings, and a platform that bridges REST and SMPP translates between them. On SMPP a receipt arrives as a deliver_sm PDU whose esm_class marks it as a receipt; its short-message field carries a text receipt with id, stat and err fields, and the message_state TLV carries the numeric state. The HTTP DLR lifts those into JSON: the SMPP stat token maps to the canonical status enum, the err value maps to the error field, and the receipt's submit/done times map to submitted_at/done_at. Understanding this mapping is what lets one delivery-handling code path serve both transports; the SMPP receipt format and the deliver_sm PDU are documented in detail in the SMPP reference.

*SMPP delivery receipt to HTTP DLR field mapping*

| SMPP receipt field | HTTP DLR field | Note |
|---|---|---|
| id (receipt) | message_id | Correlates to the original submit_sm / POST |
| stat (DELIVRD/UNDELIV/EXPIRED/REJECTD) | status | Token maps to the canonical enum |
| err | error | Operator/network error code |
| message_state TLV | status (numeric backing) | SMPP numeric state behind the token |
| submit date / done date | submitted_at / done_at | Latency and reconciliation timestamps |

## Frequently asked questions

### What is in a delivery-status (DLR) webhook payload?

The server-assigned message_id that correlates it to your send, a status drawn from a canonical enum (accepted, enroute, delivered, undelivered, expired, rejected, unknown), an error code and human-readable reason for failures, and timestamps for when the message was submitted and when the terminal status occurred.

### How is an inbound (MO) event different from a DLR?

A DLR is the asynchronous outcome of a message you sent. An inbound (MO) event is unsolicited — a message a subscriber sent to your number or a reply to a campaign — carrying from, to, the text, encoding and, for multipart messages, concatenation fields. They share transport and signing but have different schemas, so consumers branch on the event type first.

### Why standardise the delivery-status enum?

Because raw operator responses vary enormously. Mapping them to a small canonical enum with clear terminal-and-success semantics lets one consumer handle every operator uniformly, and lets you decide from the status alone whether to suppress a bad destination, resend a transient failure, or record a confirmed delivery.

### How do I stop a late 'accepted' overwriting an earlier 'delivered'?

Use the self-ordering fields. DLR payloads carry a timestamp and often a sequence number; the consumer compares them to the last state it recorded for that message id and discards any event that is older than what it already has, so an out-of-order intermediate state never clobbers a terminal one.

### How does an HTTP DLR map to an SMPP deliver_sm receipt?

They carry the same information. On SMPP the receipt is a deliver_sm PDU marked in esm_class, with id, stat and err fields and a message_state TLV. The HTTP DLR maps stat to the status enum, err to error, and the receipt timestamps to submitted_at/done_at — so one delivery-handling path can serve both transports.

### What fields let me reassemble a multipart inbound SMS?

The concatenation fields: a reference that ties the parts together, the part index and the total count. Because inbound SMS obeys the same encoding and segmentation rules as outbound, a long inbound message arrives as several MO events that the consumer stitches together in order using those fields.

## Sources

- [IETF RFC 9110 — HTTP Semantics](https://datatracker.ietf.org/doc/html/rfc9110)
- [IETF RFC 8288 — Web Linking (Link header)](https://datatracker.ietf.org/doc/html/rfc8288)

## Related

- [Webhook delivery & reliability](https://www.broadnet.me/reference/webhook-delivery-and-reliability)
- [Webhook signature verification](https://www.broadnet.me/reference/webhook-signature-verification)
- [SMPP delivery receipts (DLR)](https://www.broadnet.me/reference/smpp-delivery-receipts-dlr)
- [SMPP deliver_sm](https://www.broadnet.me/reference/smpp-deliver-sm)
- [BroadNet webhooks guide](https://www.broadnet.me/developers/webhooks)
