Skip to content
whatsapi.sh
Theme
Docs

Errors that tell you what to do

Every error is a JSON envelope with a stable code, a message that names the fix, and Retry-After on anything that clears by waiting. This page is the table your retry logic needs.

The shape

429 Too Many Requests
Retry-After: 42

{
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Daily send limit reached (20/day on Free). It clears at midnight, or Starter sends 60/day",
    "messageKey": "serverErrors.dailyCapReached",
    "details": { "scope": "day", "limit": 20, "resetAt": "2026-08-25T03:00:00.000Z", "retryAfterSecs": 42 }
  }
}

messageKey is stable, for your own translations. message is the English fallback. Refusals that need an upgrade name the cheapest plan that lifts them, because an error with no way forward is not an error message.

Every code

400 VALIDATION_ERRORA field was wrong. `details` lists paths and rules, never your values.
401 UNAUTHORIZED / INVALID_TOKENThe key is missing, wrong, or revoked. Send `Authorization: Bearer wa_…`.
402 PAYMENT_REQUIREDA limit only a purchase lifts, like more numbers or batch sends. Waiting will not clear it.
403 FORBIDDENThe account is suspended.
404 NOT_FOUNDNo such resource, or it is not yours.
409 NUMBER_NOT_CONNECTEDThe number has no usable WhatsApp session. Open Numbers, check WhatsApp on the phone, then reconnect it. Retrying the send now will not help.
409 CONFLICTThe request needs action or is already in that state. A restricted number needs `recoveryConfirmed: true`, only after you check that WhatsApp works on the phone. A request with the same `Idempotency-Key` may still be running: wait for `Retry-After`, then repeat it with the same key.
422 IDEMPOTENCY_KEY_REUSEDThis `Idempotency-Key` was already used for a different request. Send a new key. Waiting will not clear it.
429 RATE_LIMIT_EXCEEDEDToo fast. `Retry-After` says exactly how long to wait.
429 QUOTA_EXCEEDEDDaily cap, monthly quota, or a number's new-conversation budget. `details.scope` says which one: `day`, `month` or `newConversations`. `resetAt` says when it clears.
429 PAIRING_COOLDOWNToo many pairing-code requests for this number. `Retry-After` says when you can ask for another code. This is not a fixed restriction timer.
502 GATEWAY_ERRORWhatsApp rejected it upstream. Retrying might work.
504 GATEWAY_TIMEOUTWhatsApp did not confirm in time. Check the chat and, if present, watch for `details.messageId` on the `message.status` webhook before sending again. A retry with the same `Idempotency-Key` gets the same answer back without a second send.
503 SERVICE_UNAVAILABLEA dependency is down. Retry shortly.

When your number is down

A phone can lose its connection, and a new account may have no number yet. New sends then answer 409 NUMBER_NOT_CONNECTED without a charge. Messages already queued that have not left pause until the number reconnects. They are not marked failed just because the number went offline, and they resume with the configured gap. message.failed is for a send that cannot proceed or whose outcome is unknown, not for a paused queue.

  • Keep a second channel. Send what someone depends on by email too, or by email when WhatsApp refuses.
  • Before you offer something that only works over WhatsApp, like a sign-in code, check that a number is connected. Keep the answer for a minute, and drop it when a send comes back NUMBER_NOT_CONNECTED.
  • When no number is connected, hide that option and point to email instead. Don't show a button that can only fail.
  • If you have a webhook, number.disconnected arrives when the number is still down after 15 minutes of retries, and number.connected when it's back.
GET /v1/numbers
import { Whatsapi } from "@whatsapi.sh/sdk";

const wa = new Whatsapi({ apiKey: process.env.WHATSAPI_KEY! });

const numbers = await wa.listNumbers();
const whatsappReady = numbers.some((n) => n.status === "connected");

Rules for retrying

Honor Retry-After on a 429. Never auto-retry a 402, because it clears by purchase, not by waiting. On a 504, do not blindly resend: the message id in details will appear on your message.status webhook if it landed, and resending without checking is how a person gets messaged twice. To retry a send safely, give it an Idempotency-Key header and repeat it with the same key: you get the first answer back instead of a second message. The rate limits are protection, not bureaucracy, and avoiding bans explains how to stay under them in the first place.