Delivery Statuses
POST /messages and POST /omnimessages return 202 Accepted as soon as the platform takes the message. Everything that happens afterwards is reported through two complementary surfaces:
- Webhooks (push, recommended) — the platform calls your HTTPS endpoint with a
messageStatusevent on each state change. - EDRs (pull) — Event Detail Records, the per-message delivery log queryable via
GET /edrsby message id or by time range.
Message statuses (MessageStatus)
The status field in messageStatus webhook events takes exactly these values:
| Status | Meaning |
|---|---|
sent |
Successfully handed to the channel/operator. |
delivered |
The operator or app confirmed delivery to the recipient. |
displayed |
The user opened the message (channels with read receipts: RCS, WhatsApp, Viber). Plain SMS does not report this state. |
failed |
The channel rejected the send. |
undeliverable |
The operator could not deliver (e.g. unreachable subscriber). |
unknown |
The channel did not report a final state. |
Lifecycle
stateDiagram-v2
[*] --> accepted: 202 from POST /messages
accepted --> sent: handed to channel
sent --> delivered: confirmed by operator/app
delivered --> displayed: user opened (read-receipt channels)
sent --> failed: channel rejected
sent --> undeliverable: operator could not deliver
delivered --> [*]
displayed --> [*]
failed --> [*]
undeliverable --> [*]
For an SMS the typical happy path is accepted → sent → delivered. The successOn parameter (sent / delivered / seen) tells the platform which of these states counts as success for a step inside an omnimessage cascade.
Operator-level statuses in EDRs (mdnStatus)
Each EDR row also carries mdnStatus — the delivery status in the operator’s own wording, alongside the boolean lifecycle flags isSent / isDelivered / isSeen:
mdnStatus |
Meaning |
|---|---|
ENROUTE |
Accepted by the SMSC and still in transit. |
DELIVRD |
Delivered to the handset. |
EXPIRED |
The delivery window expired before the message reached the handset. |
DELETED |
The message was deleted. |
UNDELIV |
Not delivered — invalid number or unreachable subscriber. |
ACCEPTD |
Accepted by the SMSC. |
UNKNOWN |
Status is unknown. |
REJECTD |
Rejected by the SMSC (for example no route). This is not an API-key or scope error — those return HTTP 401. |
IM_EXPD |
Instant-message expiry. |
How the two levels relate
- The webhook
statusis the platform-normalized state — one vocabulary across all channels. mdnStatusis the raw operator verdict recorded on the EDR — use it (together witherrorMessage) to diagnose why a message ended inundeliverableorfailed.- A
deliveredwebhook corresponds to an EDR withisDelivered: trueandmdnStatus: "DELIVRD"; negative outcomes surface asUNDELIV,EXPIRED, orREJECTDwith the details inerrorMessage.
Choosing push vs pull
| Webhook | GET /edrs |
|
|---|---|---|
| Latency | Real time | On demand |
| Best for | Production status tracking | Reconciliation, audits, cost reporting |
| Requirements | Public HTTPS endpoint, idempotent on messageId (duplicates possible after retries) |
None |
| Limits | — | Time-range mode requires timeFrom (Alaris start_date). timeTo is optional; if omitted, the window is one hour after the start. Page size is capped at 100 |
Webhook target resolution: the per-request webhook field wins, then the Sender’s webhookUrl, then the webhook on the API key. If none is set, status events are not delivered anywhere and GET /edrs is the only way to retrieve them.
A practical polling pattern when you cannot host a webhook: query GET /edrs by messageId, stop polling a message once isDelivered/isSeen is true or mdnStatus shows a final negative state, and use time-range queries (with offset/limit paging) for periodic reconciliation.
What an EDR row contains
messageId, channelType, senderId, destination number, country/countryCode/network (operator routing), body (masked when the send used isOtp: true), your clientInfo tag, rate and finalCost (see Balance & pricing), isSent/isDelivered/isSeen, mdnStatus, errorMessage, and the timestamps time, timeSent, timeDelivered, timeSeen.
For cascades: pass the per-step messageIds to GET /edrs — there is no lookup by omniTransactionId, so keep the step IDs from the POST /omnimessages response.
Related API
See the API Reference: GET /edrs, POST /messages, POST /omnimessages.