Errors
A request that fails gets a 4xx or 5xx status and one error object. Act on its code: codes never change meaning, though new ones may be added. Handle a code you don’t know by its status.
The error object
| Field | Always there | What it is |
|---|---|---|
code | Yes | What went wrong, as one of the codes in the table below. |
message | Yes | What went wrong, in plain English for a developer. |
field | Sometimes | A JSON path to the field at fault, for example
template.name. Left out when no single field is at fault. |
details | Sometimes | More about the error, for the codes whose row below lists details keys. |
request_id | Yes | The X-Request-ID of the response. |
Status codes
Section titled “Status codes”What each status means, and whether to send the request again.
403Forbidden
The key, your plan or the workspace doesn’t allow this.
ip_not_allowedscope_missingfeature_not_in_planworkspace_closed
405Method not allowed
The path exists, but not for this method. The Allow header lists the methods it takes.
409ConflictRetry
A duplicate, the wrong state, or the same key still running.
413Too large
A file or an upload is over its size limit, or the request is over 15 MB. A JSON body over its endpoint’s limit is a 400.
415Unsupported media type
The file type isn’t accepted, or the request body is compressed.
422Unprocessable
Understood, but a rule failed: the window, a template, a limit.
invalid_fieldinvalid_phoneidempotency_mismatchwindow_closedtemplate_not_approvedtemplate_paramstemplate_ambiguouscontact_opted_outnumber_unavailablelimit_reached
500Internal errorRetry
Something went wrong on Soosh’s side. Retry with the same Idempotency-Key.
503UnavailableRetry
A service Soosh depends on is down. Retry after the Retry-After seconds.
Error codes
Section titled “Error codes”Every code, the status it comes with, and what to do. Each has its own link, such as #window_closed.
| Code | Status | What happened and what to do | Retry |
|---|---|---|---|
invalid_request | 400 | The request is malformed: bad JSON or multipart, an unknown field, a wrong type, a bad cursor, an Idempotency-Key that isn’t 1 to 255 printable ASCII characters, or a JSON body over 1 MB (4 MB for POST /v1/broadcasts). Fix the request. | No |
unauthorized | 401 | The key is missing, malformed, unknown, revoked or expired. The reason isn’t revealed. | No |
ip_not_allowed | 403 | The client IP isn’t in the key’s allowed IPs. Call from an allowed address. | No |
scope_missing | 403 | The key lacks the scope this endpoint needs. The detail names it. Comes with details.scope. | No |
feature_not_in_plan | 403 | Your plan doesn’t include the feature this endpoint needs. The detail names it. Comes with details.feature. | No |
workspace_closed | 403 | The workspace is closed, so its API doesn’t work. | No |
not_found | 404 | There is no such path or object. It is also returned for an object in another workspace. | No |
method_not_allowed | 405 | The path exists, but not for this method. The Allow header lists the methods it does accept. | No |
conflict | 409 | The object is a duplicate. The detail is the ID of the existing object. Comes with details.id. | No |
invalid_state | 409 | The object’s state doesn’t allow this, for example cancelling a completed broadcast. | No |
idempotency_in_progress | 409 | A request with the same Idempotency-Key is still running. Wait, then retry with the same key. | Retry |
too_large | 413 | A file is over the size for its type, an upload is over the upload limit, or the whole request is over 15 MB. A JSON body over its endpoint’s limit is invalid_request instead. | No |
unsupported_media_type | 415 | The file isn’t a type the API accepts, or the request body is compressed. Request bodies can’t have a Content-Encoding: send them as they are. | No |
invalid_field | 422 | A field broke a rule. field names it. The detail is set only when WhatsApp refuses a template: see Create a template. Comes with details.id. | No |
invalid_phone | 422 | The phone number isn’t valid. Check the number and send it again. | No |
idempotency_mismatch | 422 | The Idempotency-Key was used before for a different request, or by another of your API keys. Use a new key. A key rotated from the first one counts as the same key. | No |
window_closed | 422 | The contact hasn’t written to this number in the last 24 hours. Send a template instead. See The 24-hour window. A retry with the same Idempotency-Key replays this error, so use a new key once the customer has written. | No |
template_not_approved | 422 | The template isn’t approved by WhatsApp yet. Use an approved template. | No |
template_params | 422 | The template’s variables are missing or extra, and the details say which. For a broadcast, rows lists the bad rows. Comes with details.missing, details.unexpected, details.rows. | No |
template_ambiguous | 422 | Two of your numbers have a template with that name and language. Add number_id, or send the template’s id. | No |
contact_opted_out | 422 | The contact opted out of marketing messages. Don’t send them a marketing template. | No |
number_unavailable | 422 | The number isn’t active. Use an active number. | No |
limit_reached | 422 | You’ve reached a limit on your workspace. The details name it, give its value and say how much you’ve used. Comes with details.limit, details.limit_value, details.used. | No |
rate_limited | 429 | Too many requests. Wait Retry-After seconds, then try again. | Retry |
internal_error | 500 | Something went wrong on Soosh’s side. Retry. For a POST, use the same Idempotency-Key. | Retry |
unavailable | 503 | A service Soosh depends on is down. Try again after Retry-After seconds. | Retry |
No code matches.
When a message fails later
Section titled “When a message fails later”Send a message answers 201 once the message is checked and queued. WhatsApp can still turn it down after that, for example when the number isn’t on WhatsApp.
Those failures come as a message.status webhook with status: failed, and an error whose code is WhatsApp’s number (such as 131026), not one of the codes above: the request itself worked. Message statuses lists the common reasons.
A message still pending after 15 minutes fails the same way, with the error.message “Not sent in time” if it was never sent, or “WhatsApp didn’t confirm this message in time” if WhatsApp never said what became of it.
{
"message_id": "8c2e4f6a-1d3b-4a5c-9e7f-2b4d6f8a0c1e",
"status": "failed",
"at": "2026-10-14T09:30:15.902Z",
"error": {
"code": 131026,
"message": "Message undeliverable"
}
}