Skip to main content
WhatsApp Business Calling adds voice calls to the same WhatsApp number you use for messaging. A contact calls you from inside a WhatsApp chat, or you call a contact who has granted call permission. This page explains the model behind the capability; the step-by-step walkthrough lives in the WhatsApp Business Calling guide.

Meta’s availability blocklist

Meta does not permit Business Calling in every country, and the platform enforces that availability before any call is attempted. At enablement time the country of your WABA phone number is checked against the blocklist; a number in a blocked country refuses to enable calling at all. At the time of writing, the blocked countries are US, CA, EG, VN, NG. Meta updates that list over time, so the dashboard surfaces the live version under Channels → WhatsApp → Calling rather than asking you to rely on a static document. Because the block is applied when you enable calling on a number, not when you place a call, every subsequent API call inherits the decision — an outbound call attempt on a blocked number fails fast during the pre-flight checks with WHATSAPP_CALLING_COUNTRY_BLOCKED instead of being torn down after ringing.

The permission state machine

A contact has one permission state at a time for each of your WABA phone numbers. Only a contact in granted state can receive an outbound call; every other state refuses the call attempt with WHATSAPP_CALLING_NO_PERMISSION in pre-flight. Grants move through this state in one of three ways, mirrored from the WhatsApp Business Calling guide:
  • Template-authorized grant — the contact replies to a call_permission_request template. The grant has no expiry; it persists until the contact revokes it. This is the purpose=call_permission_request template tag described in Template lifecycle.
  • In-app prompt — Meta’s own prompt inside the WhatsApp client. These grants are often time-bound (for example 24 hours), so they transition to expired when the TTL lapses.
  • Consent button — an explicit wa_call_permission_grant_* button the contact taps. Persistent, like a template grant.
You never write this state yourself — it is maintained from Meta’s lifecycle webhooks and the platform’s own grant ingestion. The requested state exists so the dashboard’s permission indicator can tell “a request is pending” from “we have no grant at all”: the Call button on a conversation stays disabled either way, but the hint differs. The platform lists templates of the call_permission_request purpose under GET /api/v1/whatsapp/templates?purpose=call_permission_request, so a re-engagement prompt sequence is a query away instead of a guess. Permission state mutations arrive on your webhook endpoint as whatsapp.call.permission_granted and whatsapp.call.permission_revoked events.

Per-second metering with a one-minute floor

Calls are metered per second, so a 5-second answered call bills one minute — the billing floor. A failed or unanswered call is not billed at all; the cost is stamped to zero so retries and webhooks agree on idempotency. The two rules together mean you can cost-expose a call at initiation without being surprised by jitter or a dropped attempt. Before a call is placed, a pre-flight balance check reserves the worst-case spend — max_call_minutes × per-minute rate — and refuses with WHATSAPP_CALLING_INSUFFICIENT_BALANCE when the wallet cannot cover it. The optional daily cap adds a per-organisation guard against runaway spend; it returns WHATSAPP_CALLING_DAILY_CAP_EXCEEDED while calls are refused. Per-second metering with the floor is the whole story for the call leg itself. The optional recording item is billed separately, as its own line, so you can refund the recording even when the call leg stands. The guide’s billing section walks the low/mid/high tier table and wallet-currency conversion.

Media relay and lifecycle webhooks

Signalling and media take different paths. Outbound or inbound, the call is set up with Meta via its Cloud API; the audio leg then bridges Meta’s WebRTC (DTLS-SRTP) side to your RTP side using a relay programmed through an rtpengine NG-control endpoint. Without the relay host configured, the whole lifecycle still completes — the webhook flow, billing, and the call record — but the media leg is never wired and the call is signalling-only. The guide’s media relay section points at the two environment variables that bind it. Every state transition above lands as a webhook event on your tenant endpoint, signed with the same HMAC scheme as the rest of the platform:
  • whatsapp.call.received — an inbound call is ringing.
  • whatsapp.call.accepted — the inbound call was answered.
  • whatsapp.call.connected — an outbound call reached the contact.
  • whatsapp.call.terminated — the call ended, carrying duration, end reason, and final cost.
  • whatsapp.call.permission_granted / whatsapp.call.permission_revoked — the contact’s state changed.
The payload joins to the persisted call row by wa_call_log_id, so you can reconcile webhook delivery with the persisted record independently of retries.

Optional opt-in recording

Recording is opt-in per call — you pass recording_consent: true only where you have the right to record, and the platform treats it as a separate billable item rather than a flag on the call leg. The compliance posture is yours: the feature is a tenant-owned control, never defaulted on. Billing separation means a refund of the recording does not touch the call leg and vice versa.

See also