Errors worth branching on
These failures cover every phase of the WhatsApp Business Calling lifecycle — the outbound initiate (POST /api/v1/whatsapp/calling/calls), the media update (PATCH /api/v1/whatsapp/calling/calls/:id/session), the hangup (DELETE /api/v1/whatsapp/calling/calls/:id), and the disposition tag (POST /api/v1/whatsapp/calling/calls/:id/disposition). Each block below is a full { error, meta } envelope as the API returns it, and the matrix at the bottom answers retry vs surface for the same classes. For the platform-wide decision table these branches plug into, see the error handling guide.
401 — Unauthorized
401 on this page is the bearer key failing before the route ran — it never means the resource is wrong. Rotate the key or re-mint the scoped token; retrying the same request changes nothing.
403 — Forbidden
403 means the key authenticated but the operation is gated by scope — check the key’s scopes on the developer page; a 403 is never a data-not-found shape.
422 — Schema
422 means the payload did not match the request schema — branch on error.details.field and resend with the MSISDN in E.164 form instead of blind-retrying the same body.
422 — Call permission not granted
429 — Rate
Retry-After header and error.details.retry_after are both set on every 429 — resend the SAME request after the lower of the two. The initiate pre-flight also rejects with WHATSAPP_CALLING_DAILY_CAP_EXCEEDED (429) once the tenant’s daily calling cap is crossed — same branch, different window.
402 — Feature gate
WHATSAPP_CALLING_INSUFFICIENT_BALANCE when the wallet is below the worst-case per-minute estimate at initiate pre-flight — top up the wallet, then retry.
60-second retry matrix
Worked request and response samples
Copy the request as written, substitute your own ids, and compare the response envelope — a success in the same shape the error sections spell out. The chain below walks one call end to end: initiate → webhook → media update → hang up + disposition → history and cost roll-up.1. Start a WhatsApp voice call
POST /api/v1/whatsapp/calling/callsfrom_phone_number_id, and the SDP offer when your media bridge already has one; omit sdp_offer and the callee side exchanges its offer via the accept step instead. The tenant’s own recording-consent and callback-context fields are optional.
Request
meta_call_id is the session id — the webhook events, the media update, and the hangup all join on it.
2. Webhook event on ring
Subscribe towhatsapp.call.connected — it fires when the recipient’s client rings, one per call leg, and includes the same meta_call_id so you join it to the initiate response. The sibling lifecycle events whatsapp.call.accepted (media path live) and whatsapp.call.terminated (call ended) carry the same payload shape.
3. Update session media
PATCH /api/v1/whatsapp/calling/calls/:meta_call_id/session4. Hang up and tag a disposition
Two steps — end the call, then file the tenant-owned outcome code your dialer or QA workflow reports on. Hang upDELETE /api/v1/whatsapp/calling/calls/:meta_call_idPOST /api/v1/whatsapp/calling/calls/:meta_call_id/disposition5. Call history and cost roll-up
Per-call cost rides on each history row (cost_amount_cents / cost_currency); the working-day aggregate for a destination or WABA comes from the analytics endpoint.
Per-call history
GET /api/v1/whatsapp/calling/callsGET /api/v1/whatsapp/calling/analyticscost_amount_cents over the matching rows for a per-destination roll-up, or read totals.total_cost_cents for the WABA-wide 30-day number.