Skip to main content

Troubleshooting: WhatsApp Business Calling pre-flight gates

An outbound WhatsApp Business call refuses to dial runs through a fixed chain of pre-flight gates, and the error code on the response names the exact gate that fired. This page maps each symptom back to that gate and the fix you own, then covers the platform-side codes for call control, initiation, and media provisioning that fire after the gates pass. The happy-path setup — enabling calling, permission sources, initiating a call — lives on the guide page WhatsApp Business Calling. This page is what you reach for when a code starts with WHATSAPP_CALL or WHATSAPP_CALLING shows up in a response, log, or the dashboard KPI panel.

1. Symptom map

2. The pre-flight gate chain, in order

Every POST /whatsapp/calling/calls walks these gates in this order. The early gate returns first — a 422 permission code means the country and wallet gates passed, and a 429 means country, permission, and wallet all passed.
  1. Connection lookup + access-token decrypt. The from_phone_number_id must resolve to a connection with a working access token. Unknown id → 404 WHATSAPP_NOT_CONNECTED. A row with no resolved WABA id, or a token that fails to decrypt (key rotation, manual edit, re-encryption drift) → 422 WHATSAPP_CONNECTION_INVALID. Fix: reconnect the number through Embedded Signup under Channels → WhatsApp.
  2. Country blocklist. Meta does not yet permit WhatsApp Business Calling from every country. The gate compares the destination (and, on the enable toggle, the origin phone) against the current blocked list — US, CA, EG, VN, NG at the time of writing. A hit → 422 CALLING_REQUIRES_ELIGIBLE_PHONE with a details payload that lists the blocked codes, the phone country that triggered, and any calling-eligible phones already on your WABA. Fix: dial from an eligible phone on the same WABA, or add one. Meta updates the list periodically; Channels → WhatsApp → Calling shows the live set in the dashboard.
  3. Calling-enabled flag on the WABA phone. Refused with the same 422 as the country gate when calling has not been enabled on the origin number. Fix: enable it with POST /whatsapp/connections/{phoneNumberId}/calling/enable (owner / admin only) before dialing.
  4. Recipient is a known contact. The to number must match an existing contact — the platform never auto-creates one at call time. Unknown recipient → 422 WHATSAPP_CALLING_NO_PERMISSION with a message that names the missing contact. Fix: create the contact first.
  5. Recipient call-permission grant. The contact must hold an active granted row for the (contact, WABA, phone-number-id) triple. Missing, expired, or revoked → 422 WHATSAPP_CALLING_NO_PERMISSION, message say which of the three fired. Fix: send a call_permission_request template first; the dashboard’s contact panel offers that template as a one-click CTA when it sees a missing grant. Inbound calls are not affected — this gate is outbound only.
  6. Wallet cover. The gate prices the worst-case spend (max_call_minutes × per-minute rate for the destination country; max_call_minutes defaults to 60 and is overridable per org at Settings → WhatsApp → Calling → Max call minutes). Balance below the estimate → 402 WHATSAPP_CALLING_INSUFFICIENT_BALANCE, message names the required amount and current balance. A balance-read failure → 503 with the same code and a message to retry — the posture is fail-closed by design, so a wallet outage never debits a call the platform cannot price. Fix: top up under Settings → Billing.
  7. Per-tenant daily cost cap. When the org set one (organizations.settings.whatsapp_calling_daily_cap_cents), the gate compares today’s UTC spend against it. Over → 429 WHATSAPP_CALLING_DAILY_CAP_EXCEEDED. Fix: raise the cap under Settings → WhatsApp → Calling → Daily spend cap or wait for the next UTC day.
  8. Meta round-trip. All gates pass; the call dials. Only the codes in section 4 below can appear past this point.
Inbound calls run a narrower chain — gates 1, 2, and (on the shared-phone path) the shared-WABA gates of section 3. Recipient permission and wallet gates are outbound-only.

3. The shared-WABA resolver

When you dial through the platform’s shared calling phone rather than your own WABA, two gates join the chain at the resolver stage, either of which short-circuits the rest:
  • WHATSAPP_NO_CALLING_PHONE (422) — the resolver found no callable phone at all: no eligible WABA of yours and the shared phone unset or deregistered at Meta. Fix: run Embedded Signup and enable calling on your own number, or retry once the platform shared phone is live again.
  • WHATSAPP_SHARED_CALLING_QUALITY_PAUSED (503) — the shared phone reports quality_rating=RED from Meta and the platform pauses all shared-origin calls until it recovers. Fix: dial through your own WABA to bypass, or wait for the platform to clear the pause. Quality probes are best-effort — a probe failure fails open.

4. Past-gate codes (call control, initiation, media)

  • WHATSAPP_CALL_INITIATE_FAILED — Meta rejected the POST /{phone-number-id}/calls initiation after the pre-flight chain passed. Check the request body — in particular sdp_offer if you set it — then escalate to support with the response request_id if the shape is clean.
  • WHATSAPP_CALL_ACTION_FAILED — Meta rejected a call-control action (pre_accept, accept, reject, terminate) on an inbound or outbound call. Retry the action against the same meta_call_id; a terminated-call id is terminal, so a 5xx retry loop returns the same code until the call ends.
  • WHATSAPP_CALL_PERMISSION_FAILED — Meta refused a permission-request send, usually because the service window for the contact is closed. Re-engage the contact with a template first, then resend the call_permission_request.
  • WHATSAPP_CALLING_PROVISION_FAILED — toggling the calling-enabled flag (enable/disable) or re-registering the webhook failed at the Meta round-trip. Retry once; escalate with the phone-number id when it persists.
  • WHATSAPP_CALLING_MEDIA_NOT_PROVISIONED (503) — the platform’s media edge for the WA-calling relay is down. All signalling passed; only the audio leg refused. This is platform-side — retry and, if it persists, check the status page; nothing on your account can fix it.

5. Retry class map

6. Where each code surfaces

  • API response. The error.code field on the JSON body, with HTTP status in the table above. Error surfaces that carry a details payload (the country gate, the wallet gate) stuff the blocked list, the triggering country, or the balance numbers there so the dashboard can render the path forward without another round-trip.
  • Dashboard KPI panel. The Calls tab at /messages/whatsapp shows a row per attempted outbound call with the gate code; the permission gate also drives the Call button’s disabled state and its tooltip (missing, expired, or revoked) in the conversation header.

7. Escalation payload

When none of the fixes above resolves it, include all of the following in the support ticket — each gate’s failure is deterministic given four inputs, so a complete record resolves in one pass:
  • The full response body (the error.code, error.message, and any error.details) and the response’s request_id.
  • The request you sent: to, from_phone_number_id, and whether the shared platform phone or your own WABA was targeted.
  • The contact id for the recipient and its current permission state (from GET /whatsapp/calling/permissions?contact_id=...).
  • The UTC day and time the refusal fired, for daily-cap reports.

See also