WhatsApp API: track a message's delivery status
Last updated: 11 October 2026 · 6 min read
After sending, a message's status changes within a few seconds. You can read it with a request or receive it automatically with a webhook.
Steps
-
1
Statuses
sent: Meta accepted it and it is on its way to the phonedelivered: it reached the customer's phoneread: the customer opened and read itfailed: it could not be sent; the reason is in theexternal_errorfield
A
sentthat arrives right after sending does not yet mean delivered. -
2
Read the status with a request
curl -s "https://api.clomni.ai/v1/accounts/{account_id}/whatsapp/messages/{message_id}" \ -H "Authorization: Bearer <API_ACARI>"message_idis theidfrom the send response. The response has the same structure as the send response. You can send this request with the key of any user of the account. Checking repeatedly (polling) works, but a webhook is more efficient. -
3
Set up a webhook
In the dashboard, on the Settings › Workspace settings › Integrations page, click Configure on the Webhook card, then Add new webhook. Enter your server's address, choose Message updated (
message_updated) in the Events list and click Create webhook. The steps are described in detail in the article “Set up a webhook”. -
4
What the webhook sends
{ "event": "message_updated", "id": 918234, "status": "delivered", "message_type": "outgoing", "whatsapp_message_id": "wamid.HBgMOTk0NTAxMjM0NTY3...", "conversation": { "id": 1042 }, "inbox": { "id": 159, "name": "Company WhatsApp" }, "sender": { "phone_number": "+994 50 123 45 67" } }idis the message id from the send response; store it in your own database under this id.status:sent,delivered,read,failed. For a failed message the reason comes in thecontent_attributes.external_errorfield.- Clomni sends this request once and does not repeat it. Your server must return 2xx within 5 seconds: put the request in your own queue and answer at once.
-
5
A report to your own URL for each message (status_callback)
Add a
status_callbackblock to the send and Clomni posts that message's status straight to your URL. Nothing to set up in the dashboard.{ "channel_id": 159, "phone_number": "+994501234567", "template": { "name": "otp_code", "language": "az", "parameters": { "1": "4821" } }, "source_reference": "otp-10422-1791700000", "status_callback": { "url": "https://example.com/whatsapp/status", "after_seconds": 8, "on_change": true, "secret": "<GIZLI_ACAR>" } }url(required):https://only. Private network addresses are refused and redirects are not followed.after_seconds(1-3600): one report N seconds after the send, with the message's status at that moment.on_change(true/false): one more report every time the status changes (delivered,read,failed). If Meta refuses the send (a wrong number, for example),failedarrives this way too.- At least one of
after_secondsandon_changeis required. secret(optional): the report is signed with it. We store it encrypted and never return it.
The request your server receives:
{ "event": "message_status", "trigger": "scheduled", "message_id": 918234, "source_reference": "otp-10422-1791700000", "phone_number": "+994501234567", "status": "delivered", "delivered": true, "read": false, "sent_at": "2026-10-11T08:00:01Z", "delivered_at": "2026-10-11T08:00:04Z", "read_at": null, "error": null, "after_seconds": 8, "checked_at": "2026-10-11T08:00:09Z" }trigger:scheduled(the after_seconds report) orstatus_change(an on_change report).delivered: truemeans two grey ticks: the message reached the phone, so the phone was online.- Still
sentafter N seconds: the phone is offline, has no internet, or WhatsApp has not been opened for a while. on_change reports the delivery when it happens. readonly arrives if the customer has read receipts on. Its absence does not mean the message was not read.- Meta usually reports a status within 1-5 seconds, so 8-15 seconds is a good
after_seconds.
Signing and retries work as for call webhooks:
X-ChatX-Delivery,X-ChatX-Timestamp,X-ChatX-Signature(sha256=+ HMAC-SHA256(secret, "<timestamp>.<raw body>")). Your server should answer 2xx within 5 seconds. On no connection, a timeout, 429 or 5xx we try again after 10 seconds and after 1 minute.X-ChatX-Deliveryis the same on every attempt, so use it to drop repeats.Bulk sends take the same block: each number gets its own report, and the delay counts from that number's send.
GET /messages/{message_id}also returnsdelivered_at,read_atandstatus_callback(without the secret). -
6
Get the customer's reply too
If you also choose the Message created (
message_created) event, the customer's replies and taps on template buttons reach your system too. That is exactly when the 24-hour window opens: start sending the next notifications as free text from here.