Build notes · Reliability
The WhatsApp broadcast that quietly lost its tail
Every HTTP client is taught the same rule, and it is a good rule. A 4xx is your fault, so do not retry it. A 429 or a 5xx is the server's problem, so back off and try again. Meta breaks it, quietly, in the one place it costs you the most.
The WhatsApp Cloud API returns its throughput caps as ordinary 4xx responses. Not 429. A client that implements the standard rule correctly will take a message that was refused by a cap due to clear within the hour, mark it permanently failed, and never look at it again.
TL;DR
Three codes are transient but look permanent. 130429, 131048 and 131056 are throughput and quality caps. All three arrive as HTTP 400.
On a broadcast this eats the tail. Messages sent before the cap engages deliver. Everything after it is refused, marked failed, and never retried. The front half worked, so it reads as a partial-delivery bug rather than a rate limit.
Nothing in the response says "cap". There is no header, no distinct status, no field. The only way to know is to recognise the code.
We publish the full table. 16 codes with their handling, CC-BY, generated from the classifier on our live send path.
Why this is worse than a normal bug
A cap engages partway through a send. That timing is what makes it invisible. If throughput limits refused the whole broadcast you would notice in seconds, because nothing would arrive. Instead the first tranche delivers normally and the remainder fails, so every signal you have says the integration works. Delivery reports look plausible. The failure count is non-zero but so is the success count, and the failures carry a 400, which every dashboard in the world renders as "bad request, check your payload".
In our case the code was doing exactly what a careful engineer would write. Meta's own documentation describes these as errors rather than as rate limits, and the response body gives no hint. It surfaced in a deliberate blind-spot pass over the send and receive paths on , not from a complaint, which is its own small lesson: nobody had reported it, because from the outside a broadcast that reached most people looks like a broadcast that worked.
The three codes
| Code | HTTP | What it actually is |
|---|---|---|
| 130429 | 400 | Throughput cap on the account. Purely a rate limit; nothing is wrong with the message. |
| 131048 | 400 | Spam rate limit, driven by recipient signals such as blocks and reports. Transient, but also a quality warning: the cap is a symptom, and pushing through it risks the number rating. |
| 131056 | 400 | Pair rate limit between your business and one specific contact. Scoped to that conversation rather than the account. |
Every other common code really is permanent for that message, which is why the naive rule survives so long: it is right about nine cases out of twelve, and wrong about the three that matter during a large send.
The fix is a taxonomy, not a retry loop
The instinct is to add a retry. That is the wrong shape, because "retry" is only one of five different things a WhatsApp error can mean, and conflating them causes its own damage. An expired token retried per contact turns one account-level outage into thousands of individual failures. An unreachable recipient retried forever burns quota and drags the number quality rating down.
So the classifier answers five separate questions rather than one boolean:
src/services/whatsapp-cloud.mjs
// Throughput/spam-rate/messaging-tier caps. Transient at the ACCOUNT level, not a
// reason to give up on the contact. Meta returns these as HTTP 4xx (not 429), so
// without this set they fall through the generic "4xx = don't retry" rule and get
// permanently failed within minutes, with no resume once the cap clears.
const RATE_LIMIT_ERROR_CODES = new Set([131048, 131056, 130429]);
Alongside it: SUPPRESS_ERROR_CODES for contacts who can never be reached, NEEDS_TEMPLATE_ERROR_CODES for the 24-hour window, and AUTH_ERROR_CODES for 190, which is deliberately not retryable and deliberately not a suppression either, because the right response is to raise an alarm about the account rather than to blame the contact.
The whole table, free
Every vendor publishes what an error code means. We could not find one that publishes what to do with it, so here is ours: 16 codes, each with whether to retry, suppress the contact, switch to a template, or raise an account-level alert. Licensed CC-BY, CORS open, versioned.
The handling flags are not hand-maintained. They are generated by running classifyWaError, the same function our live send path calls, so the dataset cannot describe behaviour this software does not actually have.
What to check in your own integration
If you send at any volume through the Cloud API, three questions are worth answering today. Does your error handling branch on the Meta code, or only on the HTTP status? If a send fails, can you tell afterwards whether it was refused by a cap or rejected on its merits? And when a broadcast reports partial delivery, is there anything in your data that would let you distinguish a rate limit from a bad payload?
If the answer to the last one is no, that is the same blind spot, and it does not announce itself. A broadcast that reached most people looks like a broadcast that worked.
Sending WhatsApp at volume?
Klaros runs on your own WhatsApp Business Account through Meta's Cloud API, billed by Meta directly with no per-message markup in between. The error taxonomy above is the one it uses in production, and the dataset is free whether or not you ever use the product.
+91 97893 77634 · you message first, so nothing reaches you without your say-so.
Questions about WhatsApp Cloud API rate limits and retries
Does the WhatsApp Cloud API return 429 when you hit a rate limit?
No, and that is the trap. Meta returns its throughput caps as ordinary HTTP 4xx responses, not 429. Codes 130429, 131048 and 131056 all arrive as 400. Any client applying the standard rule that 4xx is a client error and must not be retried will permanently fail those messages, even though the cap clears on its own.
Which WhatsApp error codes are actually retryable?
Three: 130429 (rate limit hit), 131048 (spam rate limit hit) and 131056 (pair rate limit hit). Everything else in the common set is permanent for that message. 131026 and 133010 mean the contact is unreachable and should be suppressed rather than retried. 131047 and 131051 mean the message type is wrong for the window, so the fix is to send an approved template. 190 is an expired token and is account-level, so retrying or suppressing contacts only hides an outage.
Why does a WhatsApp broadcast deliver to some contacts and fail for the rest?
A throughput cap that engages partway through the send. The messages before the cap deliver normally, and the ones after it come back as 4xx. If those are treated as permanent, the tail of the broadcast is marked failed and never retried, which looks like a partial delivery bug rather than a rate limit. Nothing in the response body says a cap was involved.
What is the difference between 131048 and 131056?
Scope. 131048 is a spam rate limit applied to the account, usually driven by recipient signals such as blocks and reports, so it is also a quality warning rather than only a throughput one. 131056 is a pair rate limit between your business and one specific contact, so it affects that conversation rather than the account. Both are transient and both arrive as 4xx.
Is there a machine-readable list of WhatsApp error codes with retry behaviour?
Yes. We publish one at /whatsapp-api-errors.json under CC-BY: 16 codes, each with whether to retry, suppress the contact, switch to a template or raise an account-level alert. The handling flags are generated by running the same classifier our live send path uses, so the dataset cannot describe behaviour the software does not have.
Written 3 September 2026 about a fix shipped 5 July 2026. We append when the facts change. Related: the session that logged itself out 377 times, the full error directory, all build notes.
