Skip to content

Message status

After sending a message, you can check its status through the API at any time. The status shows how far processing and delivery on WhatsApp have progressed.

Status table

StatusDescription
pendingTurbo Notify received the message and it is waiting to be processed.
sentThe message was sent to WhatsApp successfully.
deliveredThe message was delivered to the recipient’s device.
readThe recipient read the message.
failedThe send failed. See the error object ({code, message, hint}) for details.

Check the status

To check a message’s status, make a GET request with the message_id returned when you sent it.

turbo-notify.sh
curl -X GET \
https://api.turbonotify.com/v1/messages/msg_0198d3c5a1b24d3e9f01a2b3c4d5e6f7/status \
-H "Authorization: Bearer YOUR_KEY"

Response examples

Pending

HTTP status: 200
{
  "message_id": "msg_0198d3c5a1b24d3e9f01a2b3c4d5e6f7",
  "status": {
    "state": "pending",
    "at": "2025-07-16T10:00:00Z",
    "error": null
  }
}

Sent

HTTP status: 200
{
  "message_id": "msg_0198d3c5a1b24d3e9f01a2b3c4d5e6f7",
  "status": {
    "state": "sent",
    "at": "2025-07-16T10:00:02Z",
    "error": null
  }
}

Delivered

HTTP status: 200
{
  "message_id": "msg_0198d3c5a1b24d3e9f01a2b3c4d5e6f7",
  "status": {
    "state": "delivered",
    "at": "2025-07-16T10:00:05Z",
    "error": null
  }
}

Read

HTTP status: 200
{
  "message_id": "msg_0198d3c5a1b24d3e9f01a2b3c4d5e6f7",
  "status": {
    "state": "read",
    "at": "2025-07-16T10:01:12Z",
    "error": null
  }
}

Failed

HTTP status: 200
{
  "message_id": "msg_0198d3c5a1b24d3e9f01a2b3c4d5e6f7",
  "status": {
    "state": "failed",
    "at": "2025-07-16T10:00:03Z",
    "error": {
      "code": "number_not_connected",
      "message": "The number is not connected to WhatsApp.",
      "hint": "The number was not connected to WhatsApp. Reconnect it, then send the message again."
    }
  }
}

Failure details

When the status is failed, the error object holds the details. It has the same structure as the data.error of the *.failed webhooks, so the event and the lookup describe the failure the same way (see Webhooks and Get a message):

FieldDescription
error.codeStable code meant for your code: decide how to handle the failure from this value (e.g. number_not_connected, whatsapp_send_failed, media_fetch_failed).
error.messageHuman-readable description of the failure, for your logs and for debugging. It may carry a diagnostic from WhatsApp in English; personal identifiers (phone numbers, JIDs) never appear in it. Do not decide how to handle the failure from this text: it may change, the code is what stays stable.
error.hintstring: actionable guidance to fix the problem, derived from the code. Absent (key omitted, never null) when there is no guidance for the code.

On this endpoint the error field is always present: it is filled in when state is failed and null in the other states (pending/sent/delivered/read). In GET /v1/numbers/{ref}/messages/{id}, on the other hand, the field is omitted when there is no failure.

Possible errors

StatusError codeDescription
401not_authenticatedThe API key is invalid or missing.
403insufficient_scopeThe API key does not have the messages:read scope (the response includes required_scope).
404not_foundThe message was not found, belongs to another organization, or belongs to a number other than the one in the URL (on the shortcut route without {ref}, the main number): all of these cases return the same response, with no distinction.
404number_not_found(only on the route with {ref}) The alias or id in the URL does not match any registered number.
400invalid_number_id(only on the route with {ref}) The value in the URL starts with num_ but is not a valid number id.
500internal_errorUnexpected internal server error.

Retention

The status stays available for 12 months, even after the message content expires at your plan’s retention period: only content expires. The periods per plan are in Retention.

When the status changed

The time of the most recent state change comes in the status.at field, which moves forward with each change (sent → delivered → read):

  • In GET /v1/numbers/{ref}/messages/{id}/status the status object carries {state, at}: status.at is the moment the current state was reached.
  • In GET /v1/numbers/{ref}/messages/{id} the same status.at appears inside the status object. Do not confuse it with the at at the message’s root level: that one is when the message happened (sent message: the actual send on WhatsApp; received message: when it was received) and does not change as the message moves on to delivered/read.
  • In the message.sent, message.delivered and message.read events (webhook and polling via GET /v1/events) the time of the action comes in data.at: the same field name in every message.* event.

Real-time updates

To receive every status change without polling the API, set up a webhook. See how in Webhook events.