WABridges

Errors and retries

One error shape, a handful of status codes, and the two headers that make a failed call safe to send again.

Every error has the same shape

Whatever went wrong, the body is the same object. error is prose for a human reading a log; code is the stable, machine-readable one.

Branch on code, never on the message text and never on the message alone - wording gets improved, codes do not.

โ† 4xx / 5xx
{
  "error": "bridge is not connected",
  "code": "not_connected"
}

What each status means here

The codes are used narrowly, so each one points at a different fix.

  • 400 - a missing or malformed field. Your payload is wrong; retrying unchanged will fail again.
  • 401 - missing or invalid bearer token. See API keys.
  • 402 - no subscription on the account, or payment past due. A human has to act in the dashboard.
  • 403 - the account bridge limit is reached.
  • 404 - no such bridge, message or contact.
  • 409 - conflicting state, such as pairing a bridge that is already paired.
  • 429 - over the rate limit. Wait for Retry-After.
  • 502 - WhatsApp itself rejected or failed the request.
  • 503 - the WhatsApp session is not connected. See stay connected.

The split worth internalising: 4xx other than 429 means fix the request, 429 and 503 mean wait and repeat, 502 and 500 mean try again and alert if it persists.

Retrying without sending twice

A connection that dies mid-request leaves you unable to tell whether the send happened. Idempotency-Key removes the doubt: the first use performs the call and stores the response for 24 hours, and a repeat with the same key replays that stored response instead of sending again.

A replay is labelled, so you can tell the two apart in your own logs. A duplicate that arrives while the first is still in flight gets 409 idempotency_conflict - wait and retry rather than treating it as a failure. Only successful responses consume a key, so a call that failed can be retried with the same one.

curl
curl -X POST https://wabridges.com/api/instances/user-123/proxy/send/text \
  -H "Authorization: Bearer $WA_API_KEY" \
  -H "Idempotency-Key: order-4417-confirmation" \
  -H "Content-Type: application/json" \
  -d '{"chat": "15550001234", "body": "Your order shipped"}'
โ† 200 ยท X-Idempotency-Replayed: true
{
  "message_id": "ACE41E988162AB19A4A7BB7A9E663693",
  "timestamp": 1777180605
}

Derive the key from the thing you are doing - an order id, a notification row id - not from a fresh UUID per attempt. A key generated inside the retry loop is a new key every time and protects nothing.

Backing off when rate limited

Sending and reading allow 120 requests per minute; creating bridges allows 10. Over either, the response is 429 with a Retry-After header giving the seconds to wait.

Honour that header rather than guessing an interval. Retrying immediately keeps the window full and extends the block, and a fleet of workers all retrying on the same fixed timer will collide again on every cycle - spread them out.

When you need us to look

Every response carries an X-Request-Id header. Log it next to your own request records, especially for failures.

If you send a well-formed one yourself - 1 to 64 characters of letters, digits, dashes or underscores - it is echoed back, so your id and ours are the same string and correlating the two sides is trivial. Anything else is replaced with a generated one. With that id, support can find the exact request instead of asking you three follow-up questions.

Something here not matching what you see? Write to us - a person answers.