Message status and history
Track queued messages until they are sent or failed.
Status lifecycle
Every send returns a `message_id`. Store that ID with your own order, user, ticket, or notification record.
The status endpoint returns sender, receiver, timestamps, attempt count, WhatsApp message ID when available, and the latest error message when delivery fails.
`wamid` is the WhatsApp-side message ID. It is usually null while a message is queued and can remain null when delivery fails before WhatsApp accepts the message.
- queued: accepted and waiting for delivery.
- sent: delivery flow succeeded.
- failed: validation, sender state, limit, or delivery failure stopped the message.
- Stop polling: stop when status is `sent` or `failed`.
- Polling interval: start around every 5 seconds, then back off to 15-30 seconds for long-running queued messages.
- Long queued state: check worker health, sender connection, daily limits, and monthly quota.
Endpoint reference
Use the same API key used to send the message. A service key can also read status for admin workflows.
/api/messages/{message_id}Return current delivery status and debug fields for a message.
| Parameter | Type | Description |
|---|---|---|
x-api-keyrequired | header | API key that can access the message workspace. |
message_idrequired | path uuid | Message ID returned by a send endpoint. |
{
"message_id": "2b7a0bd5-1a0d-4e6c-85b1-a7f44f92dfb0",
"workspace_id": "f2aa1d49-9f4b-4f37-9918-d8d8f13fb530",
"sender_id": "9f4b5d4c-0000-4000-9000-123456789abc",
"api_client_id": "38b7bbd3-0631-4ea1-a93b-09d560a645d3",
"receiver_number": "212612345678",
"message_text": "Your appointment is confirmed.",
"status": "sent",
"wamid": "wamid.HBgM...",
"source_system": "appointments",
"source_reference": "booking_1042",
"error_message": null,
"attempts": 1,
"queued_at": "2026-06-29T16:12:01.000Z",
"sent_at": "2026-06-29T16:12:04.000Z",
"failed_at": null,
"created_at": "2026-06-29T16:12:01.000Z"
}Polling workflow
Use polling when webhooks are not configured or when the user is actively waiting for a result. For background reconciliation, prefer webhooks.
Do not poll forever. Store the last known status and move old queued messages into an operator review state if they do not change after your business timeout.
async function waitForMessage(messageId: string) {
for (let attempt = 0; attempt < 12; attempt += 1) {
const response = await fetch(`${process.env.HOOKMESSAGE_BASE_URL}/api/messages/${messageId}`, {
headers: { "x-api-key": process.env.HOOKMESSAGE_API_KEY! },
});
const status = await response.json();
if (status.status === "sent" || status.status === "failed") {
return status;
}
await new Promise((resolve) => setTimeout(resolve, attempt < 3 ? 5000 : 15000));
}
return { status: "review_required", message_id: messageId };
}Need help?
Use Book Integration Help if you want setup help for OTP or automated customer messaging.
Continue building
Next steps
Start here
A plain-language map of Free Sandbox, paid sender modes, credentials, and the first successful request.
Free Sandbox quickstart
The shortest beginner path from a verified account to a queued Free Sandbox OTP.
Glossary
Understand workspaces, keys, senders, receivers, queue states, webhooks, and idempotency.
Authentication and access
HookMessage separates customer API keys, dashboard sessions, and internal platform access.