Webhooks & Delivery Reports
A successful send means "accepted for delivery", not "delivered". Delivery reports (DRs) arrive asynchronously as webhooks keyed by the transaction_uuid you set on the message.
Receive a delivery report
Point a public HTTPS endpoint at MessageEagle and handle the callback:
ts
app.post("/webhooks/messageeagle", (req, res) => {
const { transaction_uuid, status } = req.body;
// 1. Acknowledge fast — return 2xx before doing slow work.
res.sendStatus(200);
// 2. Look up the original message by transaction_uuid and update its status.
queue.enqueue({ transaction_uuid, status });
});Build for the hard case
Webhooks are at-least-once. Design every handler accordingly.
- Idempotent updates. Key on
transaction_uuid; applying the same DR twice must be a no-op. Prefer an UPSERT over an INSERT. - Acknowledge first, process later. Return
2xximmediately, then do the real work on a queue. Slow handlers cause redeliveries. - Tolerate out-of-order. A
deliveredcan arrive before asent. Use a status precedence, not last-write-wins.
Verify the source
Validate the request signature (or a shared secret) before trusting a payload. Never act on a webhook body you can't authenticate.
Status model
| Status | Meaning |
|---|---|
queued | Accepted, awaiting dispatch. |
sent | Handed to the carrier. |
delivered | Confirmed on the handset. |
failed | Carrier rejected or could not deliver. |
expired | TTL elapsed before delivery (e.g. unreachable handset). |
Voice completion
Voice sessions emit completion callbacks too — recordings, transcripts, DTMF logs, and cost land on the VoiceSession. Poll GET /organization/{organizationId}/webhook-deliveries as a fallback if a callback is missed — see Webhook Deliveries.
