Response stream

Receipts and delivery verdicts

Delivered, read and not delivered — the stream's receipt and delivery_failed frames, the webhooks that carry the same facts, and the question you can ask about one message now.

A command frame tells you how a command ended. For message.send, succeeded is the transport's settlement of the send — the platform handed the message on and the transport took it. It is not a delivery receipt, and nothing about whether the message reached the recipient's device arrives on that frame.

That arrives later, if the transport reports it, as one of two facts:

  • Delivered or read — a recipient's device reported it. On the stream this is a receipt frame; on a webhook, message.delivered or message.read.
  • Not delivered — the provider accepted the message and later reported it undelivered, Apple's "Not Delivered" on iMessage. On the stream this is a delivery_failed frame; on a webhook, message.delivery_failed.

Both frames are opt-in, and so is each webhook: a client that never asked for them meets nothing new.

Asking for them

Name the frames you want in events when you open the stream. The values combine:

curl -N "https://api.mapier.ai/v1/response_stream?events=receipts,delivery_failures" \
  -H "Authorization: Bearer $MAPIER_API_KEY" \
  -H "x-mapier-version: 2026-10-15" \
  -H "Accept: text/event-stream"

receipts adds the receipt frame and delivery_failures adds the delivery_failed frame. A value the stream does not frame is a 422 naming it, never a stream that silently lacks what you asked for. Both frames carry an id: like every other, so a reconnect with Last-Event-ID replays the ones you missed — see Resuming.

receipt — delivered or read

id: 0192a7c4-7d55-7a90-b3e1-4c6a8f2d9e04
event: receipt
data: {"commandId":"3f1a0c2e-8b47-4d19-9a5e-2c6f0b8d4e71","messageId":"0192a7c4-5a31-7bd2-8e6f-1d0c9b8a7f65","status":"delivered","occurredAt":"2026-08-24T18:03:14.000Z","simulated":false}

Correlate by commandId, the handle the send's 202 gave you. status is delivered or read, and a receipt never walks a message backwards: once a message is read, a late delivered for it moves nothing and frames nothing. occurredAt is when the device reported it. The full field list is on the reference.

What each transport can report differs, and the frame says only what was reported:

  • In test, the sandbox produces receipts according to each contact's delivery mode, and every one carries simulated: true.
  • A live iMessage line frames them from Apple's status of the send, which is first asked about 75 seconds after the send and last about 10 minutes after it. A delivery or a read later than that is never framed, and a read arrives only from a recipient who shares read receipts.

So the absence of a receipt is not evidence of anything. A message with no delivered frame may have been delivered after the window closed.

delivery_failed — not delivered

id: 0192a7c4-8e20-7f43-a1b6-9d3c5e7f0a28
event: delivery_failed
data: {"commandId":"3f1a0c2e-8b47-4d19-9a5e-2c6f0b8d4e71","messageId":"0192a7c4-5a31-7bd2-8e6f-1d0c9b8a7f65","occurredAt":"2026-08-24T18:05:02.000Z","simulated":false}

The provider accepted the message and later reported it not delivered. Three things about it are easy to get wrong:

  • It is not a failed send. The message keeps its status, and the frame carries no status, errorCode or failureClass — the send did not fail, its delivery did. Do not route it through the code that handles a failed command frame.
  • It can arrive before the send's own command frame. Live, a verdict can land while the transport's settlement of the send is still on its way, and the command frame follows it. Correlate by commandId, and do not assume you recorded the send before the verdict.
  • Nothing retries it. The platform does not resend, and it does not mark the message failed. What to do next is yours; a resend is a new message under a new Idempotency-Key.

In test, a contact whose delivery mode is Sends, then not delivered rehearses it: the send settles succeeded, and once the world's delivery delay has passed the verdict arrives with simulated: true. There the verdict always comes after the command frame; live it need not, which is the difference to design for.

The webhooks that carry the same facts

Subscribe an endpoint on the project's Webhooks page to the types you want. Each carries the same fact as its frame, with the time under a name of its own:

Stream frameWebhook typedata
receiptmessage.deliveredmessageId, conversationId, commandId, deliveredAt, simulated
receiptmessage.readmessageId, conversationId, commandId, readAt, simulated
delivery_failedmessage.delivery_failedmessageId, conversationId, commandId, failedAt, simulated

Every data also carries projectId and environment. A webhook needs no opt-in beyond the subscription, and it is retried when your endpoint does not answer — see Webhooks.

Asking about one message now

GET /v1/messages/{messageId}/receipts asks the transport what it can prove about one message at the moment you call it. It stores nothing and moves nothing, and its delivered and read are three-valued: null means the transport does not know, which is not the same as false. Use it when a person is looking at one message and wants the current answer; use the frames or the webhooks to keep your own records.

On this page