WhatsAble

Error Handling

Handle Whatsable API v1 errors: read the error body, decide whether to retry, and retry safely with Idempotency-Key.

Every Whatsable API v1 error has the same body, and every code is listed with its fix on API v1 errors. This page shows how to act on an error in your code.

Read the error body

A 4xx or 5xx response looks like this:

{
  "error": {
    "type": "permission",
    "code": "insufficient_scope",
    "title": "...",
    "message": "...",
    "how_to_fix": "...",
    "owner": "caller",
    "retryable": false,
    "docs_url": "https://docs.whatsable.app/api/errors#insufficient_scope",
    "request_id": "..."
  }
}
  • Branch on code. It is stable. The title, message and how_to_fix are for people, and their wording can change.
  • retryable says whether the same request can succeed later without a change.
  • owner says who must act: caller (you change the request or the account), whatsable, or meta.
  • request_id identifies the request. Give it to support when you ask about one.

The API v1 overview explains every field.

A send can fail with 201

A send that Whatsable recorded answers 201 Created, even when WhatsApp refused the message. Always read status in the body:

  • status: "sent": WhatsApp accepted the message.
  • status: "failed": read error. It has the same code, retryable and how_to_fix fields as an error response.

When to retry

What you gotWhat to do
429 rate_limitedWait the seconds in the Retry-After header, then retry. See rate limits.
An error with retryable: trueRetry later, with a growing delay between attempts.
An error with retryable: falseDo not retry. Fix the cause that how_to_fix names, or show it to the person who can.
201 with status: "failed" and retryable: trueSend again later with a new Idempotency-Key.
201 with status: "failed" and retryable: falseDo not send it again.

Retry safely with Idempotency-Key

Send an Idempotency-Key header on every POST that creates something, such as a send. Use a new key for each new message, and the same key for each retry of it. The API remembers a key for 24 hours.

  • A 4xx or a 503 is not stored. Fix the request if needed and retry with the same key. The retry runs.
  • A 2xx or a 500 is stored, because the message may already have gone out. A retry with the same key returns the stored answer and never sends twice.
  • The same key with a different body gets 409 idempotency_key_reused.

Example

async function sendTemplate(apiKey, idempotencyKey) {
  const response = await fetch('https://api.whatsable.app/v1/messages', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,
    },
    body: JSON.stringify({
      to: '+15551234567',
      type: 'template',
      template: { name: 'order_update', language: 'en', variables: { 1: 'Ana', 2: '#1042' } },
    }),
  });

  const body = await response.json();

  if (!response.ok) {
    const { code, retryable, how_to_fix } = body.error;
    if (response.status === 429) {
      const waitSeconds = Number(response.headers.get('Retry-After') ?? 1);
      return { retryAfterSeconds: waitSeconds };
    }
    return { failed: code, retryable, howToFix: how_to_fix };
  }

  if (body.status === 'failed') {
    return { failed: body.error.code, retryable: body.error.retryable, howToFix: body.error.how_to_fix };
  }
  return { messageId: body.id };
}

The template's variable names come from Read a template. The full request is on Send a message.