A WhatsApp send failure is one of several distinct states. How to separate a failed send from a failed delivery from a message held for quality assessment.
Key takeaways
- The Cloud API documents five status values for an outbound message: `sent`, `delivered`, `read`, `failed` and `played`. The errors array appears only when the status is `failed`.
- A 200 response from the send call is not a delivery. It means the request was accepted, and sometimes that the message was deliberately held.
- A phone number sent without a plus sign and country calling code can inherit your own business number's country code, producing a recipient who does not exist.
- No webhook arriving at all is a finding in its own right, and it points at a completely different failure path than a `failed` status.
- "Unreachable" is a conclusion that requires a repeated 131026 on a correctly formatted international number, not a single attempt.
A failed WhatsApp Cloud API message is not one fact but one of several distinct states, and each demands a different response. The message can be held before it leaves, leave and never arrive, or arrive without ever telling you. Marking the customer unreachable is the last conclusion in the chain, not the first.
The five states an outbound message can be in
Meta's documentation defines five possible values for the status field on an outbound message webhook, and each one means something entirely different about what happened. Two other fields in the same object give you indirect evidence: errors is included only when the status is failed, and pricing and conversation are included only with sent, delivered and read.
| Status | What it proves | What it does not prove |
|---|---|---|
sent | The message left Meta's servers successfully | That any device received it |
delivered | The message reached the user's device | That anybody opened it |
read | The message was displayed in an open chat thread on the device | That the content was understood or will be answered |
failed | A failure to send or to deliver. Here, and only here, there is an errors array | Where exactly it stopped, unless you read the code |
played | A voice message was played for the first time | Anything outside voice media |
One trap breaks every delivery report that assumes a fixed sequence: when the customer receives a message while the chat screen is already open, Meta does not send a delivered webhook at all, because delivery is implied by the read. A conversation showing read with no delivered is not a bug in your code.
What does a 200 response from the send call actually prove?
That the request was accepted, and nothing beyond that. A successful response body carries messaging_product, a contacts array with input and wa_id, and a messages array with the message identifier. Comparing input against wa_id is the cheapest check available: input is the number exactly as you submitted it, and wa_id is the identity Meta resolved it to. If the two do not match what you expected, you have found the problem before ever looking at a webhook. The build walkthrough is in how to create a WhatsApp bot.
Template messages sometimes carry an extra field, message_status, and this is the only case where a 200 accompanies a message that was never sent. The value held_for_quality_assessment means the message was deliberately held under Meta's pacing mechanism, which exists to allow time for early customer feedback on a template. For current build and running costs, see how much a chatbot costs.
Why did no webhook arrive at all?
Because an absent webhook is not a status. It is a separate failure path, and most teams lose hours to it because they go looking for an error where no event exists. Three documented causes, one of which is not about you at all:
- The message was held by pacing. Per the documentation, held messages are released and delivered if early feedback is positive, and dropped if it is negative. In the negative case every held message produces a failed webhook with error code 132015 and the template moves to
PAUSED. If Meta reaches its internal decision deadline without enough feedback, held messages are released automatically. - The webhook was never subscribed to the relevant field at the app level, or the app was never subscribed to the WABA. Those are two separate steps, and doing only one produces exactly this symptom.
- Your endpoint returned an error, or signature verification failed because the request body was parsed before the HMAC was computed. Meta sent it, you never received it, and your log holds nothing.
The check that separates the three is simple: is any inbound webhook arriving today. If customer messages reach you and status events do not, it is a field subscription problem. If nothing arrives at all, it is the endpoint.
The number, before anything else
Meta's documentation explicitly recommends including both the plus sign and the country calling code in the recipient field, and spells out what happens when you do not: your own business number's country calling code is prepended to the recipient's number. In Israel this plays out predictably. A number stored as 0526856459 and sent without a plus gets 972 prefixed to it and becomes a number nobody holds. The value that needs to go out is +972526856459 - leading zero removed, country code added.
- A number pasted from a spreadsheet with spaces, hyphens or parentheses is not the issue. The documentation states those formats are supported.
- A landline, a switchboard virtual number or a service number with no WhatsApp account behind it is a legitimate cause of 131026, and it will not change on a retry.
- A number captured from a free-text form without normalisation is the single most common source of a failure that looks like a platform problem.
- Storing the
wa_idreturned on the first successful send removes that whole family of problems from every send after it.
The order to run the checks in
- Open the message row in your own database and confirm it holds a message identifier returned by Meta. If it does not, the failure happened on your side and never reached the platform.
- Compare the
inputyou sent against thewa_idyou got back. A gap here explains everything downstream of it. - Check whether a
message_statusfield came back carryingheld_for_quality_assessment. If so, there is no failure - there is a wait. - Find the most recent webhook for that message identifier. If there is none, move to the subscription and endpoint checks rather than continuing here.
- If the status is
failed, read the wholeerrorsarray includingerror_data.details, not just the generic message field. - Classify the code. 131047 means the 24-hour window closed, not that the number is wrong. 131049 means the message was withheld to maintain healthy ecosystem engagement. 131026 is a genuine delivery failure.
- Only if 131026 recurs on a normalised number, across more than one attempt separated in time, mark the record as unreachable on this channel.
Step six is the one that changes outcomes. Codes 131047 and 131049 both produce a failed status exactly like a real delivery failure, and neither says anything about the number itself. The full code list and the practical meaning of each is in the WhatsApp Cloud API error codes reference, and the queue behaviour under volume is covered in running a high-volume WhatsApp message queue.
When "unreachable" is genuinely unreachable
| Evidence you hold | Conclusion you are allowed to draw |
|---|---|
| A single 131026 on an un-normalised number | None. Fix the format and try again |
| 131026 recurring on an international-format number, on two separate dates | This channel does not reach this customer |
| 131047 | The customer is entirely reachable. The 24-hour window closed and an approved template is required |
| 131049 | Withheld for ecosystem reasons, not for recipient reasons |
sent with no follow-up for days | The device was not connected. This is not evidence of a wrong number |
| No webhook at all | Nothing was measured. The problem is on your receiving side |
Of the six pieces of evidence in that table, exactly one justifies flagging the customer as unreachable. Flagging too early removes an active customer from every future list, which is far more expensive than one more send attempt. If the failures cluster around templates rather than numbers, start from the WhatsApp template approval guide instead.
Sources
Frequently asked questions
I got `sent` and then nothing for two days. Is the message lost?
Not necessarily. `sent` means the message left Meta's servers and delivery is waiting for the recipient's device to connect. A phone that is switched off or out of coverage produces exactly this picture. It is not evidence of a wrong number, and it does not justify resending the same content.
Why do I see `read` without `delivered` on the same conversation?
Because when the customer receives the message while the chat screen is already open, Meta does not send the `delivered` webhook at all, and delivery is implied by the read. A report that assumes a fixed three-event sequence will flag that conversation as an anomaly when it is documented, entirely correct behaviour.
Is a 200 from the send call enough to record "sent to customer"?
No. It records that the request was accepted. On template messages it can arrive alongside a `message_status` of `held_for_quality_assessment`, meaning the message was deliberately held. Your business record should be driven by the `delivered` webhook, not by the HTTP status code of the API call.
How do I tell our own bug from a platform problem?
By the spread and by the class of code. A structural code such as 131008 or 131009 points at your request. A sudden rise in 131026 across many numbers at once, immediately after a change to your normalisation code, points almost every time at the normalisation rather than at the customers.
Keep reading
Related service
WhatsApp Cloud API
Templates, a two-way inbox and reminders on the official Meta API.
About the author
Yehonatan Saadia
Freelance automation, web & MVP developer
I'm Yehonatan Saadia, a senior developer who builds business automation, custom websites, and MVPs for small and mid-sized companies across the US, Europe, and Israel. These guides come from real client work, not theory.
Work with meHave a project like this?
Tell me what you're trying to automate or build. I reply within 24 business hours with a few targeted questions, then we walk through it on a free 30-minute call, with no commitment. You come away with a scope, a timeline and a fixed price - or a straight answer that it isn't worth building.
