Errors

Every error code the API returns, what causes it, and what to do.

An error is JSON with a code you can branch on and an error message for people. Branch on code. The message can change. Treat a code you do not know as a generic failure.

json
{ "code": "unauthorized", "error": "invalid bearer token" }

Some codes add fields to the body, shown below. A failure on the connection to WhatsApp can also add reason, one of proxy_unreachable, proxy_auth_failed, proxy_tls_failed, proxy_timeout, proxy_dns_failed or connect_failed.

How to read the status

StatusMeaningRetry?
400your request is wrongno, fix it
401token problemno, fix the token
403the token is valid but may not do thisno
404the route or object does not existno
409the channel's state does not allow thisafter the state changes
429back-pressure or an allowanceafter the wait
500the request failed; a write may already have appliedidempotent reads: backoff; writes: inspect before retrying; report if it persists
502, 503the request or response failed; a write may already have appliedidempotent reads: backoff; writes: inspect before retrying
504outcome of a send unknownnot blindly, see send_unconfirmed

For writes, a 500, 502 or 503 does not prove nothing happened. The edge can reject a response after the operation succeeded. Inspect before retrying: list or read the state you changed, and never re-create a group without reading/listing first. A send may be delivered even when you cannot confirm it; see Recipes. Only retry a write directly when the specific error proves it was refused before being sent. POST messages/read sends receipts and is a write, despite its name.

400 Bad request

CodeCauseFix
invalid_bodythe body is not valid JSON, is empty, is over 1 MiB, carries a field the route does not take, lists a webhook receiver without a secret, or has malformed or invalid mentionssend a JSON object with only the documented fields
invalid_requestwell-formed but cannot be done as sent, for example more than 50 participants, a duplicate participant, a name that is too long, an X-Wagate-Groups-Fresh value other than required, or a mention whose phone identity cannot be resolvedread the error text, fix the value
field_not_alloweda key this route does not accept, on routes that name it (channel PATCH, history-sync)remove the key
header_not_allowedthe request carried an account headersend only the bearer token
up_to_range_too_largea read with up_to would mark too many messages or send receipts to too many sendersmark an older message with up_to first, or read without up_to; nothing was sent
quote_not_foundthe quoted message is not in this channel's logquote a message this channel has
quote_wrong_chatthe quoted message belongs to another chatquote a message from the same chat
media_too_largethe media is over the limit for its kindshrink it, or use document; limits are on Media
media_type_unsupportednot a type this kind can carryconvert it, or send as document
media_fetch_failedthe URL did not answer, answered with an error, or is not a public addresscheck the URL from outside your network

401 Unauthorized

CodeCauseFix
unauthorizedthe token is missing, too short, unknown, or does not open this channel; or the channel does not existcheck the token and the channel ID
credential_stalethe token stopped applying during the request: replaced, or the channel removeduse the current token
slot_endedthis channel's paid slot has endedrenew it in the console

A 401 carries WWW-Authenticate: Bearer.

403 Forbidden

CodeCauseFix
admin_only_fieldyour webhook list would drop a receiver whose media setting was set in the consolekeep that receiver, or remove it in the console
account_suspendedthe account that owns this channel is on holdcontact support
number_forbiddena supplied phone number could not be read as a valid numbercheck the number format; invalid mentions answer 400 invalid_body or invalid_request
blockedthis channel is blockedcontact support
channel_unentitledno paid slot, and not your sandbox channelbuy a channel in the console
media_forbiddenthe media belongs to another channel, to nobody, does not exist, or was removed by retention. One answer for all four, on purposecheck the media_id came from this channel
not_own_messageyou tried to delete a message this channel did not sendonly your own messages can be deleted for everyone
lid_lookup_not_grantedLID lookup is not enabled for this channel; only an operator can enable itdo not retry; contact support if you need it

404 Not found

CodeCauseFix
not_foundthe route does not exist (a malformed media ID counts), or webhooks/test named a URL this channel has not setcheck the path and the URL
message_not_foundthe message is not in this channel's loguse an ID from a send result or a webhook
no_history_anchorthe channel holds no message in that chat to ask for older history fromwait for a message, or send one

409 Conflict

CodeCauseFix
not_linkedthe channel has no linked phonelink it in the console
stood_downWAgate paused this number after WhatsApp pushed backreconnect it in the console after the restriction ends
verification_failedthe subgroup WAS created, and checking it afterward failed. group_jid names itdo NOT create it again; see Communities
revoke_window_passedthe message is too old to delete for everyoneit can no longer be deleted for everyone
revoke_in_progressa delete of that message is already runningwait for it to return
history_sync_offsynced history is off for this channelturn on store_history_sync

verification_failed body:

json
{
  "code": "verification_failed",
  "error": "...",
  "verification": "...",
  "definitive": true,
  "group_jid": "[email protected]",
  "retry_as_ambiguous": false
}

429 Too many requests

CodeCauseWait
rate_limiteda flood guard refused a burst; the body has window and retry_afterRetry-After seconds
gateway_busythe gateway is working on too many requests; bound says which limit; nothing is queuedRetry-After seconds
sandbox_allowance_exceededthe sandbox channel used up an allowance; window says whichuntil resets_at
daily_send_cap_exceededthis channel exhausted a positive daily ceiling, has a zero stop, or its send counting became unreliable; the body has window, cap, sent, resets_at and retry_afterpositive ceiling: until resets_at; zero cap: contact support; inspect counting errors
whatsapp_rate_limitedWhatsApp throttled the number; no Retry-After is sentback off, retry later
daily_cap_group_addtoday's group-add allowance is used (warm-up protection is on); the body has class, cap, sent, tier and resets_atuntil resets_at
daily_cap_contact_writetoday's contact-save allowance is used (warm-up protection is on); the body has class, cap, sent, tier and resets_atuntil resets_at
daily_cap_contact_checktoday's number-check allowance is used (warm-up protection is on); the body has class, cap, sent, tier and resets_atuntil resets_at
history_request_too_soona history request for this channel or chat came too recentlyRetry-After seconds (also retry_after in the body)

Details and example bodies: Limits.

500, 502, 503, 504

CodeStatusCauseWhat to do
internal_error500WAgate could not complete the requestidempotent reads: backoff; writes: inspect before retrying; contact support if it persists
upstream_unreachable502the gateway could not reach the service behind itidempotent reads: backoff; writes: inspect before retrying
upstream_redirect502the service behind it answered with a redirectidempotent reads: backoff; writes: inspect before retrying
upstream_not_json502the service answered with something unreadableidempotent reads: backoff; writes: inspect before retrying
upstream_not_relayable502the answer could not be passed back safelyidempotent reads: backoff; writes: inspect before retrying
upstream_too_large502the media object is larger than the public API relaysuse a smaller object
media_upload_failed502the upload to WhatsApp failed and nothing was sentsafe to retry
receipt_partial502some read receipts went out before the read failed; receipted says how manydo not resend the same request
upstream_unavailable503the token could not be checked right nowretry
wagate_not_configured503this deployment has no upstream configuredcontact support
operator_unresolved503the gateway could not confirm its own account setup for a moment; nothing is wrong with your channelretry after Retry-After (5 seconds)
whatsapp_disconnected503the WhatsApp connection dropped before the request completed; the response cannot tell whether the write was senttreat any write as possibly applied; list or read the state of what you changed before retrying a write; never re-create (e.g. a group) without reading/listing first; reads can be retried after a short back-off
whatsapp_timeout503WhatsApp did not answer an info query in time; the response cannot tell whether the write was senttreat any write as possibly applied; list or read the state of what you changed before retrying a write; never re-create (e.g. a group) without reading/listing first; reads can be retried after a short back-off
read_unresolvable503a read receipt could not find the other side's addressinspect read/receipt state before retrying this write
history_unresolvable503a history read was refused rather than answered short, because the other side's second address could not be resolvedretry
history_unavailable503synced history is unavailable right nowretry later
audit_unavailable503the read could not be recorded first, so it was not servedretry
lane_reclaimed503a call reached its deadline before it finished; the body has ambiguoussee below; Retry-After: 1
send_unconfirmed504the outcome of a send is UNKNOWN; it may have reached WhatsAppsee below

The three "outcome unknown" errors

json
{ "code": "send_unconfirmed", "error": "...", "ambiguous": true, "id": "ambiguous:evt_<EVENT_ID>" }
json
{ "code": "lane_reclaimed", "error": "...", "retry_after": 1, "ambiguous": false }
json
{ "code": "receipt_partial", "error": "...", "receipted": 12 }
  • send_unconfirmed (504): the message may have been delivered. The body has ambiguous: true and, when WAgate could log the attempt, an id. That id names a row in this channel's chat log marked ambiguous: true. It is not a WhatsApp message ID, so no message.status webhook will carry it, and neither the row nor its absence tells you whether the message went out. Do not resend blindly: check with the recipient where you can, and resend only if you accept a possible duplicate. See Recipes.
  • lane_reclaimed (503): ambiguous is always present. false means the call was refused before it was sent and nothing reached WhatsApp, so a plain retry is safe. true means it was sent and the outcome is unknown: inspect before retrying a write. For a group add, read membership first; if the participant is already present, treat the add as success.
  • receipt_partial (502): some receipts went out. Do not send the same request again, or those messages are marked twice. Mark only the rest.

Full handling code for all three is in Recipes.

Warming codes

CodeStatusMeaning
warming_peer_refused403Warming refused a partner number. Contact support.
warming_cooldown409Stopped for safety. restart_after says when warming can be switched on again, or null when an operator clears it.
warming_ack_required409The account has not acknowledged the current warming text. Send acknowledge.
warming_restart_ack_required409The cooldown is over. Send restart_ack: true to switch warming back on.
warming_state_unreadable503Warming state could not be read right now. Retry.
warming_ack_version_unknown400The text_version is not the current one.
Errors · WAgate docs