waiaconnect

Documentation / Essential

Check a message's status

GET /v1/messages/:id

Markdown

Building with AI? Connect it to your account over MCP: it checks your setup, creates webhooks and templates with your confirmation and writes the code for you. See how →

GET /v1/messages/:id with the msg_… returned by POST (a numeric id → 404). One status vocabulary: queued | sending | sent | delivered | read | failed | held. ⚠ Terminal: read, failed and held (we never resend a held message: resend it yourself from heldUntil if needed). delivered is NOT terminal (delivered→read is valid) — if you stop polling at delivered you never see the read.

A rejection from WhatsApp does NOT come back as an HTTP error. The POST already answered 202 (accepted, not sent); if Meta refuses it, this is where you find out: the message ends failed (or held) with lastErrorCode, a stable code you can switch on — WINDOW_24H_EXPIRED, RECIPIENT_OPTED_OUT, CONNECTION_TOKEN_REVOKED… The lastError next to it is English help for a human, not the contract. Full catalog in the errors module.

And you don't have to ask: the webhook tells you. If Meta rejects our send (unknown template, invalid parameter, recipient opted out…), you get a message.status with status: "failed", stage: "send", the same failureCode you see here and messageId = your msg_…. Once per message. DELIVERY receipts of what you sent through the API carry connectMessageId = your msg_… (there messageId is Meta's wamid); if Meta rejects it at delivery, stage: "delivery" + failureCode, and the same code lands in lastErrorCode.

curl https://api.waiaconnect.com/v1/messages/msg_5d325780fe504d0f87eb45009d28ae4f \
  -H "Authorization: Bearer wc_live_YOUR_API_KEY"
# → { "id": "msg_…", "status": "delivered", "attempts": 1, "createdAt": "…Z", … }
const res = await fetch("https://api.waiaconnect.com/v1/messages/msg_5d325780fe504d0f87eb45009d28ae4f", {
  headers: { "Authorization": "Bearer wc_live_YOUR_API_KEY" }
});
const msg = await res.json();
console.log(msg.status); // queued|sending|sent|delivered|read|failed|held (terminal: read|failed)
<?php
$ch = curl_init("https://api.waiaconnect.com/v1/messages/msg_5d325780fe504d0f87eb45009d28ae4f");
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer wc_live_YOUR_API_KEY"],
]);
$msg = json_decode(curl_exec($ch), true);
echo $msg["status"]; // read|failed are terminal; delivered can still become read
import requests
res = requests.get("https://api.waiaconnect.com/v1/messages/msg_5d325780fe504d0f87eb45009d28ae4f",
  headers={"Authorization": "Bearer wc_live_YOUR_API_KEY"})
msg = res.json()
print(msg["status"])  # read|failed are terminal; delivered can still become read

We store no content: this response carries metadata (status, attempts, holdReason), never the message text.

The most common rejection is WINDOW_24H_EXPIRED: you sent free text to someone who has not written to you in the last 24 hours. An approved template is the way out.