Delivery reports
A 200 from the send endpoint means queued, not delivered. Delivery happens
a moment later, and the message history is where you follow it.
GEThttps://api.9bits.net/whatsapp/ng/v1/whatsapp/report/messages
curl -G https://api.9bits.net/whatsapp/ng/v1/whatsapp/report/messages \ -H "Authorization: Bearer $NINEBITS_WA_KEY" \ -d start_date=2026-08-01 \ -d end_date=2026-08-31 \ -d per_page=25{ "data": [ { "message_id": "2d665262-ffca-4289-a6cb-4126910d868b", "provider_message_id": "wamid.HBgNMjM0ODAyMjIyODUwMhUCABEYEjQ1MjI1REMy", "sender": "2349060008502", "recipient": "2348022228502", "message_type": "template", "template_name": "moviezone", "language": "en", "category": "marketing", "content": "Hi Ada, take 25% off everything until the timer runs out.", "status": "read", "direction": "MT", "cost": 85, "currency": "ngn", "created_at": "2026-08-11T20:35:54+01:00", "sent_at": "2026-08-11T20:35:56+01:00", "delivered_at": "2026-08-11T20:35:56+01:00", "read_at": "2026-08-11T20:36:00+01:00" } ], "page": 1, "per_page": 25, "total": 126}Match on message_id — the value the send returned.
Status values
Section titled “Status values”| Status | Meaning |
|---|---|
queued | Accepted and billed by us; not yet handed to WhatsApp. |
submitted | Handed to WhatsApp, awaiting their acknowledgement. |
sent | WhatsApp accepted it. A wamid now exists. |
delivered | It reached the handset. |
read | The recipient opened it. |
failed | It will not be delivered. |
Each timestamp field is populated as the message passes that stage, so
delivered_at being null on a sent message simply means it hasn’t arrived
yet.
Two ids, two jobs
Section titled “Two ids, two jobs”| Field | Whose | Use it for |
|---|---|---|
message_id | Ours | Everything on our side — looking a message up here, matching it to what your send call returned. |
provider_message_id | WhatsApp’s wamid.… | Matching against Meta’s own tooling and reports. |
Filtering
Section titled “Filtering”| Parameter | Notes |
|---|---|
start_date / end_date | YYYY-MM-DD. |
status | One of the values above. status=failed is the useful one. |
page / per_page | Defaults 1 and 25. total is the unpaginated count. |
Poll for failures across a run:
curl -G https://api.9bits.net/whatsapp/ng/v1/whatsapp/report/messages \ -H "Authorization: Bearer $NINEBITS_WA_KEY" \ -d start_date=2026-08-11 \ -d end_date=2026-08-11 \ -d status=failedInbound messages appear here too
Section titled “Inbound messages appear here too”direction distinguishes them: MT is a message you sent, MO one a customer
sent you.
Inbound messages matter beyond their content — a customer message is what opens
the 24-hour session window that lets you reply with free-form
text. The created_at of the most recent
MO message from a number is when their window started.
A failure is still billed
Section titled “A failure is still billed”WhatsApp messages are billed on acceptance, with no refunds. Everything we
can check, we check before charging you — a template that doesn’t exist, a wrong
params count, a closed session window. Those cost nothing.
What we cannot know until Meta answers — a number that no longer has WhatsApp, a
media URL that stopped resolving, a recipient who blocked you — is charged, and
then shows up here as failed.
Which numbers can I send from?
Section titled “Which numbers can I send from?”GEThttps://api.9bits.net/whatsapp/ng/v1/whatsapp/senders
[ { "id": 6, "display_name": "9bits Technologies (Live)", "phone_number": "2349060008502", "status": "active", "is_default": true, "quality_rating": "GREEN", "messaging_tier": "TIER_1K" }]Pass a sender’s id as wa_account_id on a send; omit it and the one with
is_default: true is used.