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 withWHATSAPP_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
EveryPOST /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.
- Connection lookup + access-token decrypt. The
from_phone_number_idmust resolve to a connection with a working access token. Unknown id → 404WHATSAPP_NOT_CONNECTED. A row with no resolved WABA id, or a token that fails to decrypt (key rotation, manual edit, re-encryption drift) → 422WHATSAPP_CONNECTION_INVALID. Fix: reconnect the number through Embedded Signup under Channels → WhatsApp. - 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,NGat the time of writing. A hit → 422CALLING_REQUIRES_ELIGIBLE_PHONEwith adetailspayload 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. - 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. - Recipient is a known contact. The
tonumber must match an existing contact — the platform never auto-creates one at call time. Unknown recipient → 422WHATSAPP_CALLING_NO_PERMISSIONwith a message that names the missing contact. Fix: create the contact first. - Recipient call-permission grant. The contact must hold an active
grantedrow for the (contact, WABA, phone-number-id) triple. Missing, expired, or revoked → 422WHATSAPP_CALLING_NO_PERMISSION, message say which of the three fired. Fix: send acall_permission_requesttemplate 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. - Wallet cover. The gate prices the worst-case spend
(
max_call_minutes× per-minute rate for the destination country;max_call_minutesdefaults to 60 and is overridable per org at Settings → WhatsApp → Calling → Max call minutes). Balance below the estimate → 402WHATSAPP_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. - 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 → 429WHATSAPP_CALLING_DAILY_CAP_EXCEEDED. Fix: raise the cap under Settings → WhatsApp → Calling → Daily spend cap or wait for the next UTC day. - Meta round-trip. All gates pass; the call dials. Only the codes in section 4 below can appear past this point.
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 reportsquality_rating=REDfrom 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 thePOST /{phone-number-id}/callsinitiation after the pre-flight chain passed. Check the request body — in particularsdp_offerif you set it — then escalate to support with the responserequest_idif 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 samemeta_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 thecall_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.codefield on the JSON body, with HTTP status in the table above. Error surfaces that carry adetailspayload (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/whatsappshows 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 anyerror.details) and the response’srequest_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.