Track Message Delivery
Message delivery status comes through webhook events, not the REST API response. The create endpoint (POST /accounts/:account_id/messages) returns the message object immediately — the status at that point is "sent" (queued), and what happens next depends on the carrier.
How delivery works
pending → queued → sending → sent → delivered
↘ failed| Status | Meaning |
|---|---|
pending | Message created and accepted by the API |
queued | Picked up by the send worker and queued for dispatch |
sending | Currently being transmitted to the carrier |
sent | Carrier accepted the message (message.sent webhook fires) |
delivered | Carrier confirmed delivery to the handset (message.delivered fires) |
failed | Delivery failed at any stage (message.failed fires with failure_reason) |
Status advances in one direction only. If Surge receives an out-of-order carrier status report (a rare but real occurrence on some networks), it is discarded rather than rolling the status back. A message in delivered state will never revert to sent.
Not all carriers send delivery receipts. If you never receive message.delivered after message.sent, it doesn't mean the message wasn't delivered — the carrier simply didn't confirm it. Verizon, in particular, batches delivery confirmations, which can arrive several minutes late.
Webhook-based tracking (recommended)
Configure a webhook endpoint (Settings → Webhooks in the dashboard) and handle the events you care about:
@app.post("/webhooks/surge")
async def surge_webhook(request: Request):
payload = wh.verify(await request.body(), request.headers)
match payload["type"]:
case "message.delivered":
msg = payload["data"]
await db.messages.update(msg["id"], {"status": "delivered"})
case "message.failed":
msg = payload["data"]
await db.messages.update(msg["id"], {
"status": "failed",
"failure_reason": msg.get("failure_reason")
})
return Response(status_code=200)Return 200 quickly. If your processing takes time, enqueue the work and return immediately — Surge retries if your handler times out.
See Receiving Messages & Webhooks for endpoint setup and signature verification.
Retrieving a message by ID
You can retrieve a sent message to check its current fields, but the response does not include a status field. It includes the message body, attachments, conversation thread, and metadata:
curl https://api.surge.app/messages/MESSAGE_ID \
-H "Authorization: Bearer YOUR_API_KEY"{
"id": "msg_01jrzhe8d9enptypyx360pcmxj",
"blast_id": null,
"body": "Your appointment is confirmed for Friday at 2pm.",
"attachments": [],
"conversation": {
"id": "cnv_01jrzhe8d9enptypyx360pcmxk"
},
"metadata": {}
}Use this endpoint to fetch the message body or attachment URLs if you didn't store them at send time. For status, rely on the webhook events.
Listing messages
To retrieve all messages for an account, use the list endpoint with cursor-based pagination:
curl "https://api.surge.app/accounts/YOUR_ACCOUNT_ID/messages" \
-H "Authorization: Bearer YOUR_API_KEY"{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6Ii4uLiJ9",
"previous_cursor": null
}
}Pass after=CURSOR or before=CURSOR as query parameters to page through results. The list returns messages in reverse chronological order (most recent first).