# Errors

> The source meta or whabuzz contract, the WhaBuzz error codes, and how to read Meta's errors.

Canonical: https://docs.whabuzz.com/errors/

Every error response says who produced it, in the body and in a header. Check `source` first.

| `source` | Meaning | HTTP status |
|---|---|---|
| `whabuzz` | WhaBuzz stopped the request: auth, validation, account state, WhaBuzz's own limits. Meta was not called, or could not be reached. | WhaBuzz's |
| `meta` | Meta answered with an error. WhaBuzz returns it verbatim. | **Meta's own status** |

The header `X-WhaBuzz-Error-Source: whabuzz` or `meta` repeats it, so you can branch without parsing JSON.

## WhaBuzz errors {#whabuzz-errors}

```json
{
  "source": "whabuzz",
  "error": {
    "code": "number_not_connected",
    "message": "Number 1013622581841046 is not connected.",
    "details": { "connection_detail": "no_verified_endpoint" }
  },
  "request_id": "req_01JAB3W1Z4B7D0F3H6K9N2Q5S8"
}
```

Always log `request_id` (also the `X-WhaBuzz-Request-Id` header). It is what support looks up.

| HTTP | `code` | When | `details` |
|---|---|---|---|
| 400 | `invalid_request` | Malformed JSON, missing or invalid field, missing `X-WhaBuzz-Tenant` | field errors |
| 401 | `unauthorized` | Missing, wrong, revoked or expired API key | |
| 403 | `forbidden` | The key's role cannot do this | |
| 403 | `tenant_not_approved` | Account is `pending_approval` or `rejected` | |
| 403 | `tenant_suspended` | Account is `suspended` | `reason` |
| 403 | `email_not_verified` | Console sign-in before the email link was used | |
| 404 | `not_found` | No such resource, or not yours | `reason: "payload_expired"` when a replay is outside [retention](https://docs.whabuzz.com/guides/retention.md) |
| 409 | `number_not_connected` | Send from a number that is not `connected` | `connection_detail` |
| 409 | `idempotency_conflict` | Same `Idempotency-Key` still in flight | |
| 422 | `idempotency_conflict` | Same `Idempotency-Key` with a different body | |
| 422 | `webhook_verify_failed` | Your endpoint failed the [verify ping](https://docs.whabuzz.com/guides/webhooks.md#verify-ping) | `http_status`, `body_excerpt` |
| 429 | `rate_limited` | Sign-up, login or password reset only. `Retry-After` is set. | |
| 502 | `meta_unreachable` | Graph API did not answer within 15 seconds. **The outcome is unknown.** | |
| 400 | `invalid_token` | Sign-up or password-reset link is unknown, used or expired | |
| 409 | `conflict` | The resource is in the wrong state for this action, or the WABA is already connected to another account | |
| 409 | `limit_reached` | A per-account cap, for example the number of webhook endpoints | |
| 503 | `billing_disabled` | Any `/v1/billing` route before billing is switched on | `billing_enabled: false` |
| 503 | `billing_not_configured` | Billing is on but the payment gateway is not configured | |
| 502 | `billing_gateway_error` | The payment gateway answered with an error | |
| 502 | `meta_token_invalid` | The Embedded Signup token failed Meta's validation | |
| 500 | `meta_not_configured` | WhaBuzz's Meta credentials are missing (our fault, not yours) | |
| 500 | `internal_error` | A fault in WhaBuzz. Retry; report the `request_id` if it persists. The message is always `Internal error`. | |

`meta_unreachable` is the one to handle carefully: the message may or may not have been sent. Retry with the same `Idempotency-Key` (see [Idempotency](https://docs.whabuzz.com/guides/idempotency.md)) rather than composing a new send.

## Reading Meta errors {#reading-meta-errors}

When Meta refuses a call, you get Meta's HTTP status and Meta's error object under `error`, unchanged:

```json
{
  "source": "meta",
  "error": {
    "message": "(#131030) Recipient phone number not in allowed list",
    "type": "OAuthException",
    "code": 131030,
    "error_data": {
      "messaging_product": "whatsapp",
      "details": "Recipient phone number not in allowed list: Add recipient phone number to recipient list and try again."
    },
    "error_subcode": 2655007,
    "fbtrace_id": "AqN2fZtPLm1oV0xK3wRhjQe"
  }
}
```

Reading it:

- **`error.code`** is the number to branch on. WhaBuzz never rewrites it.
- **`error.error_data.details`** is Meta's plain-language explanation. Show it in your logs.
- **`error.fbtrace_id`** is what Meta support asks for. Keep it.
- The HTTP status is Meta's: 4xx means your request or account state, 5xx means Meta's side. Do not auto-retry 4xx.

The example above, `131030`, is what you get while a WABA is in test mode and the recipient is not on its allowed list. Add the number to the list in Meta's manager, or finish business verification.

Common ones: `131047` (re-engagement: the 24-hour window is closed, use a template), `131026` (message undeliverable), `132000` series (template parameter problems), `130429` (throughput), `131056` (pair rate limit), `190` (token expired or invalid; reconnect the number).

If Meta's response is not JSON, WhaBuzz wraps it as `{"source":"meta","error":{"raw_body":"<first 4 KB>"}}`.

The complete list, with Meta's recommended fix for each, is in Meta's documentation: [Cloud API error codes](https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes).

## Errors inside events {#events}

Failures after acceptance arrive as events, not HTTP errors: a [`message.status`](https://docs.whabuzz.com/events/message-status.md) with `status: "failed"` carries `status.errors[]` in Meta's format. The same `error.code` numbers apply.
