Common WhatsApp API Errors and What They Actually Mean
Most integrations get built and tested against a happy path: valid number, connected device, small text message. The first time something goes wrong in production, the error response is the only clue you get. This is a field guide to the errors POST /v1/messages actually returns, what triggers each one, and what your code should do about it.
401 — invalid or missing API key
The most common cause isn't a wrong key, it's an expired or revoked one — for example after rotating keys in the dashboard and forgetting to update a deployed integration. A 401 should never be retried automatically: retrying with the same bad key just burns requests. Log it loudly and alert a human, since it usually means every send is failing, not just one.
400 invalid_recipient — malformed phone number
This is the single most common rejection reason on new accounts. The API expects E.164 format (+ followed by country code and number, no spaces, no leading zero after the country code). A number pasted from a spreadsheet or a form field often keeps local formatting like 06 12 34 56 78 instead of +31612345678, and the API rejects it before it ever reaches WhatsApp. If you're seeing a lot of these, validate and normalize numbers before they hit the API — see the full guide to validating phone numbers for the regex and normalization steps.
409 — session not connected
This means the WhatsApp device linked to your account isn't currently active: it was logged out from the phone, lost its connection, or was never paired in the first place. No amount of retrying fixes this — the fix is re-scanning the QR code from the dashboard. If you run unattended sends (a nightly reminder job, for example), listen for the session.disconnected webhook event so you find out about a dropped pairing before your next batch fails outright, rather than discovering it from a pile of 409s. The full pairing lifecycle — including what triggers a disconnect and how re-pairing works — is covered in how WhatsApp pairing works on TextMeFlow.
413 / 415 — media too large or unsupported type
TextMeFlow accepts media up to 100 MB per file. A 413 means the file exceeds that; a 415 means the file type or MIME type isn't one WhatsApp supports for that message type (a .docx sent as an "image" message, for instance). Compress or convert before upload rather than retrying — the file isn't going to get smaller on its own. Details on supported types and the media endpoint are in the media documentation.
429 — two different meanings
A 429 is not always the same problem. TextMeFlow returns it for two distinct reasons, distinguishable by the reason or error field in the body:
burst_exceeded/hourly_exceeded— you've hit the per-number rate limit (1 message/second, 60/minute, 1,000/hour) that protects your number from looking like a spam source. This is temporary; back off and retry. The rate limits guide has the full numbers, and handling rate limits and message status has working retry code for Node and Python.quota_exceeded— you've used up your plan's monthly message allowance (Free is 50 messages/month, paid plans start at €5/month). This one won't resolve itself; retrying does nothing until you upgrade or the billing period resets.
Treating both as "just retry" is a common bug — the second one will retry forever and never succeed.
Recipient has opted out (STOP)
If a recipient has previously replied STOP (or the local-language equivalent your flow uses), TextMeFlow's anti-spam pipeline blocks further sends to that number regardless of what your integration requests. This isn't a bug to work around — it's required opt-out handling, and re-sending to an opted-out number is also how numbers get flagged and banned by WhatsApp itself. The full anti-spam pipeline (rate limits, risk score, STOP handling, quiet hours) is described in the anti-spam documentation.
Building error handling that doesn't make things worse
A short mental model for retry logic:
- Never retry: 401 (auth), 400 (validation — the request itself is wrong), 415 (unsupported type), quota_exceeded.
- Retry with backoff: 429 with
burst_exceededorhourly_exceeded, and 5xx server errors. - Fix the root cause, don't retry: 409 session errors (re-pair), 413 (shrink the file).
Logging the raw error body — not just the status code — makes triage far faster, since TextMeFlow's error responses carry a machine-readable reason alongside the HTTP status.
Want to see these responses against a real account instead of just reading about them? Start free and send your first message in a few minutes — the free plan includes 50 messages, enough to test every error path above.
Zelf WhatsApp-berichten versturen via API?
Gratis voor altijd tot 50 berichten/maand. QR scannen en binnen 5 minuten verstuur je je eerste bericht.
Gratis voor altijd