Help
Troubleshooting
The five causes of almost every delivery problem, in the order worth checking them.
Work down this list in order. It is not sorted by how interesting each cause is. It is sorted by how often it turns out to be the answer, and the first four account for nearly every ticket. Stop at the first one that fires.
- Is the number registered on the Cloud API?
- Are the templates approved?
- Is there credit?
- Is the phone number reaching us in a usable form?
- Only then, look at the integration.
1. Is the number registered on the Cloud API?
This is the most common "nothing works at all" cause. A number can be linked, visible in the dashboard, showing its verified business name, and still be unregistered, in which case Meta rejects every send with error 133010, regardless of templates or credits.
The symptom is total and uniform: all 18 events fail, all for the same reason, from the first send onwards.
How to check
curl "https://whatsapp-api.growcord.in/api/v1/notifications/events" \
-H "Authorization: Bearer sk_live_xxx"Read health. An unregistered number names itself:
{
"health": {
"canSend": false,
"blockers": [
"This number is not registered on the WhatsApp Cloud API. Register it with a 6-digit two-step PIN (Phone Numbers → Register), until then Meta rejects every send with error 133010."
]
}
}In the dashboard, the number's row on Phone Numbers carries a Registered badge when it is done. No badge means no sends.
How to fix
On Phone Numbers, use Register 2FA and set a 6-digit PIN. If registration itself fails, the usual causes are the number still being active in the WhatsApp app, or a two-step PIN set previously that no longer matches, Connect your WhatsApp number covers both.
The other blocker that can appear here is a missing access token, which means the link has gone stale. Re-link the number and the token comes back with it.
2. Are the templates approved?
Until Meta approves a template, every business-initiated message for that event fails with 409 TEMPLATE_NOT_APPROVED. The symptom is narrower than an unregistered number: some events work and others do not, and which ones fail is stable.
How to check
curl "https://whatsapp-api.growcord.in/api/v1/notifications/events?refresh=true" \
-H "Authorization: Bearer sk_live_xxx"refresh=true re-reads approval state from Meta before answering, so it will not tell you a template is pending when Meta approved it two minutes ago. Compare ready against total, then read each entry's status and statusReason.
How to fix
NOT_CREATED, submit it:POST /v1/notifications/templates/submitwith an empty body submits everything missing.PENDING, wait. Authentication templates clear almost immediately, the rest take minutes to a few hours. Re-submitting does not restart the review; it creates a name clash.REJECTED, readstatusReason, which carries Meta's own words, fix the cause and re-submit.
Message templates and approval covers every state and the usual rejection reasons.
3. Is there credit?
A send with an insufficient balance returns 402. Nothing was sent and nothing was spent. The charge is reserved before the message is handed to Meta and reversed on any failure, so there is nothing to reconcile.
How to check
curl "https://whatsapp-api.growcord.in/api/v1/credits" \
-H "Authorization: Bearer sk_live_xxx"The 402 body itself is more useful than the balance alone, because it says what this particular message needed:
{
"error": "Insufficient message credits, a marketing message costs 8 credits. Ask your administrator to add more.",
"reason": "INSUFFICIENT_CREDITS",
"category": "MARKETING",
"creditsRequired": 8,
"price": "0.0099",
"currency": "USD",
"balance": 3
}Match on reason, never on the wording: a wallet-billed plan returns INSUFFICIENT_BALANCE with a different sentence and a decimal balance.
How to fix
Top up. Note that creditsRequired varies by category, a marketing message costs several times a utility one, so a balance that comfortably covers order confirmations can still refuse a cart-recovery campaign. A 402 is worth alerting a human about rather than retrying in a tight loop; back off for minutes, not seconds.
4. Is the phone number reaching us in a usable form?
This is where problems hide once the first three are clean, because it fails per customer rather than globally: most orders notify fine and a few silently do not. The store looks healthy and a handful of customers hear nothing.
How to check
Read the skip log in the integration, both the WooCommerce plugin and the Shopify app log skips as loudly as failures, precisely because a skipped notification is invisible to the merchant until a customer complains. A run of "no usable phone number" skips starting on a particular date usually means the store's checkout changed.
The recurring causes:
- No phone field at checkout. Many themes make it optional. No number, no notification, and nothing failed.
- A national number with no country code. Non-digits are stripped from
to, so9876543210is not rejected. It is delivered to whoever holds that number in whichever country the digits happen to resolve to. Set the integration's default country so bare numbers are normalised, and prefer E.164 (+919876543210). - Extensions, separators and free text.
+91 98765 43210 ext 4becomes a longer digit string than any real number. - A landline. It passes validation and has no WhatsApp account, so the send is accepted and never delivered.
- Fewer than five characters. Rejected by validation with a
400namingto.
How to fix
Make the checkout phone field required, set the default country in the integration's settings, and store numbers in E.164. For the customers already affected, the skip log gives you the order ids to follow up by another channel.
5. Only then, look at the integration
If the number is registered, the templates are approved, there is credit, and the phone numbers are clean, the problem is in the wiring. In that order, check:
- Is the event enabled? Both integrations ship with events individually switchable, and a fresh install does not have every one on.
- Is the queue draining? Sends are queued so a WhatsApp failure never breaks checkout. A queue depth that only grows means the scheduler is not running, on WordPress, that is usually a site with
DISABLE_WP_CRONset and no system cron to replace it. - Is test mode on? It redirects every message to one test recipient. It is the correct behaviour and it looks exactly like "no customer receives anything".
- Is the API key right, and allowed? A
401means the key is wrong, revoked or on a disabled account. A403naming an origin means the key's domain allowlist rejected the calling site, add the domain, or call from your server, which sends noOriginat all. - Did the store event actually fire? A custom order status that is not mapped to an event produces no send and no error.

Error codes at a glance
| Status | Code | Cause | Fix |
|---|---|---|---|
| 400 | , | Schema validation failed. error is a zod object, not a string. | Read fieldErrors and fix the body. Do not retry. |
| 400 | UNKNOWN_EVENT | The event key is not one of the 18. The body carries supportedEvents. | Use a catalog key. Map any other store state onto order_status. |
| 401 | , | Missing, malformed, revoked or inactive API key. | Check the Authorization: Bearer header, then the key's state in the dashboard. |
| 402 | , | Insufficient credits or wallet balance. reason distinguishes them. Nothing sent, nothing spent. | Top up. Read creditsRequired for what this message needed. Retry slowly, and alert a person. |
| 403 | , | The key's domain allowlist rejected the calling Origin. | Add the domain under API Keys → Manage, or call from your server, which sends no origin. |
| 403 | SESSION_CLOSED | The 24-hour window is closed and the message is not a template. | Send an approved template, or use /v1/notifications/send. Do not retry. |
| 404 | NO_PHONE | No configured WhatsApp number on the account. | Link a number, or pass a phoneNumberId that exists. |
| 409 | TEMPLATE_NOT_APPROVED | No approved template for this event on this number. details names the event, template and language. | Submit the catalog, then poll GET /v1/notifications/events?refresh=true. Do not retry the send. |
| 413 | , | The request body exceeded 1 MB, realistically only on a 500-recipient batch. | Split the batch. |
| 429 | , | Key rate limit when the body has only error. | Back off for RateLimit-Reset seconds, then retry. It clears inside 60 seconds. |
| 429 | , | Account sending cap when the body also has limit, usage and limits. | Do not retry. Wait for the cycle, or have an administrator raise the cap. |
| 500 | , | The API key lookup itself failed. | Retry with back-off. If it persists, raise a ticket. |
| 502 | SEND_FAILED | Meta rejected the send. The charge is refunded. 133010 is an unregistered number; 132000 is a parameter-count mismatch. | Read Meta's message. Retry with back-off and cap your attempts, each retry costs a fresh credit. |
The confusing ones
A 402 for a request that was not about money
POST /v1/messages/send reserves the charge before it validates the body. On an empty or nearly empty balance, a malformed request therefore surfaces as a 402, which sends you looking at billing when the real problem is a missing field. Nothing is charged either way; the reservation is reversed.
POST /v1/notifications/send does not have this problem, it validates first, so a bad body always returns the 400 that caused it.
If a 402 appears on a request you did not expect to cost anything, top up a small amount and send it again. If it then returns 400, the body was the problem all along.
The two meanings of 429
Two unrelated controls return the same status and need opposite handling. The distinguishing feature is the body, not the status.
Key rate limit, 120 requests a minute by default, per key. The body has error and nothing else:
{ "error": "Rate limit exceeded for this API key. Slow down or raise the key's limit." }It clears inside the 60-second window. Honour RateLimit-Reset and retry. This one is a retry.
Account sending cap, a per-minute, daily or monthly cap an administrator set, or a paused account. The body carries three extra fields:
{
"error": "daily sending limit reached (5000/5000). Ask your administrator to raise it.",
"limit": "daily",
"usage": { "minute": 12, "day": 5000, "month": 41233 },
"limits": { "perMinute": null, "daily": 5000, "monthly": null, "paused": false }
}Retrying cannot clear a daily or monthly cap and cannot unpause an account. Stop, alert someone, and resume on the next cycle. Branch on the presence of limit before you decide to retry.
TEMPLATE_NOT_APPROVED that will not clear
The 409 is checked against Meta, not only against the local mirror, a send that finds nothing locally re-reads from Meta once before giving up, so a template approved moments ago is picked up rather than failing for another hour. If the 409 persists, it is not a caching problem. Check, in this order:
- The status really is not
APPROVED. Read it with?refresh=true.PENDINGandREJECTEDboth produce this error. - You are sending from a different number than the one the template was approved on. Approval is per WhatsApp business account. With several numbers linked, an omitted
phoneNumberIdresolves to the oldest configured one, which may not be the one you submitted templates under. - The language does not match.
en_USalso matches a template approved asen, but neither matches one approved asen_GB. Thedetails.languagefield in the error says what was looked for.
Retrying the send does nothing. Poll readiness instead.
403 with a closed 24-hour window
A non-template message to a customer who has not written to you in the last 24 hours is refused:
{
"error": "The 24-hour customer service window is closed for this contact. Per WhatsApp policy, only an approved template message can start or re-open a conversation.",
"code": "SESSION_CLOSED"
}This is not a fault. It is WhatsApp policy, it applies to any customer who has never replied, and it will not resolve on retry. The window opens when the customer sends something, not when time passes. Send an approved template, or use /v1/notifications/send, which always does. The 24-hour window is the full explanation.
A message that reports success and never arrives
A 200 with a messageId means Meta accepted the message for delivery. It does not mean the customer received it. When a successful send does not arrive, it is one of these:
- The number is valid but wrong. The most likely cause by far. Non-digits are stripped from
to, so a national number without a country code is delivered to a real subscriber, just not yours. Check therecipientWaIdon the message status: it is digits only, and a number that is too short or missing a country code is obvious there. - The recipient has no WhatsApp account. A landline, or a number never registered with WhatsApp. Accepted, undeliverable.
- The customer blocked your business. Delivery stops silently; nothing is reported back.
- Their phone is off or offline. Status stays
sentand moves todeliveredwhen the device reconnects. Give it time before treating it as a failure. - Test mode is on in the integration. Every message went to the test recipient instead. This looks identical to a delivery failure from the merchant's side.
- It arrived and was missed. Check the recipient's archived chats and spam-adjacent folders before assuming it did not.
Check the delivery state with GET /v1/notifications/status/:messageId. status moves sent → delivered → read, or lands on failed. Updates arrive asynchronously, so a message read back immediately after sending almost always still says sent, that on its own is not a problem.
Still stuck
Getting support explains how to raise a ticket and what to attach so it can be answered in one reply, in short: the diagnostics bundle, the exact error body, and one affected order id.
