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

CodeMeaning
200Success.
400Invalid request (missing/!malformed fields).
401Missing or invalid API key.
403Key lacks the required scope.
404Resource not found.
409Conflict (e.g. duplicate idempotency key).
422Valid shape but not allowed (e.g. outside the messaging window).
429Rate limited — back off and retry.
5xxTransient 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 404 from a real handler (e.g. an unknown flow id). These carry a JSON error body and a Wabery-Version response 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-Version header.

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.

Errors & rate limits | Wabery Docs | Wabery