Errors & rate limits
HTTP status codes, retries, and idempotency.
The API uses conventional HTTP status codes and returns a JSON error body:
{
"error": "contact_opt_in_required",
"message": "An active WhatsApp opt-in record is required before sending to this contact."
}Status codes
| Code | Meaning |
|---|---|
200 | Success. |
400 | Invalid request (missing/!malformed fields). |
401 | Missing or invalid API key. |
403 | Key lacks the required scope. |
404 | Resource not found. |
409 | Conflict (e.g. duplicate idempotency key). |
422 | Valid shape but not allowed (e.g. outside the messaging window). |
429 | Rate limited — back off and retry. |
5xx | Transient server error — safe to retry. |
Version skew & unavailable endpoints
A 404 or 405 can mean one of two different things:
- The resource doesn't exist — a genuine
404from a real handler (e.g. an unknown flow id). These carry a JSON error body and aWabery-Versionresponse header. - The endpoint isn't served by the connected instance — the method/path
isn't deployed (your SDK is newer than the API build). These come from the
framework/proxy, so they have no
Wabery-Versionheader.
The SDK distinguishes the two for you: a 404/405 with no Wabery-Version
header is raised as a WaberyEndpointNotAvailableError (a subclass of
WaberyApiError) with a message that names the method and path and points at the
likely capability gap — rather than a bare "request failed with HTTP 404".
import { WaberyEndpointNotAvailableError } from "@wabery/sdk";
try {
await wabery.flows.sendByConfigKey("lead_intake", { channelId, to });
} catch (err) {
if (err instanceof WaberyEndpointNotAvailableError) {
// This build doesn't serve POST /flows/send. The list endpoints carry the
// same data, so fall back to them (e.g. flows.list() / projects.list()).
}
}If you call the REST API directly, treat a 404/405 without a
Wabery-Version response header as "endpoint not available on this instance"
and fall back to the list endpoint (GET /flows, GET /projects) that exposes
the same data.
Retries & idempotency
Retry 429 and 5xx responses with exponential backoff. To make retries safe,
send an Idempotency-Key header on writes — Wabery returns the original result
for a repeated key instead of sending twice:
await wabery.messages.send({
channelId: "channel_...",
conversationId: "conversation_...",
text: "Hi",
idempotencyKey: "7c3f-order-2291",
});curl https://api.wabery.com/v1/messages \
-H "Authorization: Bearer $WABERY_API_KEY" \
-H "Idempotency-Key: 7c3f-order-2291" \
-H "Content-Type: application/json" \
-d '{ "channel_id": "channel_...", "conversation_id": "conversation_...", "text": "Hi" }'Rate limits
Limits scale with your plan. When you exceed them you get 429 with a
Retry-After header (seconds) — respect it before retrying.
Webhook deliveries are retried automatically with backoff on non-2xx responses, so a brief outage on your endpoint won't drop events.