Overview
User-initiated calls (UIC) let WhatsApp users place voice calls to your business. UIC is free for the business and available in every country where Cloud API is available — no permission required. You control when calls are accepted via call_hours and whether the call button is visible via call_icon_visibility.
Consumer Device Eligibility
UIC currently accepts calls only from iPhone and Android phones — including:
- The user's primary device (their main account device)
- Companion devices that are iPhone or Android phones
WhatsApp Web, Desktop, tablets, and smart-glass companions are not supported for UIC. If a user attempts to call from those, the call won't reach your endpoint.
callback_permission_status enabled does not work on companion devices yet — only the primary device flow honors it.
Prerequisites
- Subscribe your app to the
callswebhook field. - Calling enabled on the business phone number.
- (Optional but recommended)
call_hoursconfigured if you have specific operating hours. - (Optional)
callback_permission_status: ENABLEDif you want auto-permission for callback calls.
The UIC Flow
- User taps call button on your business profile (or via Call Buttons & Deep Links).
- Call Connect webhook arrives at your endpoint with an SDP offer from Meta.
- Pre-accept (recommended):
POST /calls action: pre_acceptwith your SDP answer. Establishes the WebRTC connection without yet flowing call media. - Accept: once your WebRTC stack is ready,
POST /calls action: accept. Wait for the 200 OK before sending media. - Terminate: either party hangs up. You receive a Call Terminate webhook.
You have about 30–60 seconds after the Call Connect webhook to accept. If you don't, the call ends on the user's side with "Not Answered" and you receive a Terminate webhook.
Pre-Accept (Recommended)
Pre-accepting establishes the media connection ahead of time, avoiding the audio clipping that happens when media flows before the WebRTC channel is ready (caller misses the first few words).
POST /<PHONE_NUMBER_ID>/calls
{
"messaging_product": "whatsapp",
"call_id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA",
"action": "pre_accept",
"session": {
"sdp_type": "answer",
"sdp": "<<RFC 8866 SDP>>"
}
}
Success response:
{ "success": true }
After pre-accept, your WebRTC stack should be initiating ICE connectivity checks. Don't send media yet — wait until you've called accept and received the 200 OK.
Note: if your SDP answer in accept doesn't match the one you sent in pre_accept (same call_id), you'll get an error. Send the same SDP both times.
If you can't control when your media starts flowing (e.g. some IVR systems start playing immediately on connect), skip pre-accept and go straight to accept. Pre-accept's whole point is to frontload connection setup, but it requires you to control media timing.
Accept the Call
{
"messaging_product": "whatsapp",
"call_id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA",
"action": "accept",
"session": {
"sdp_type": "answer",
"sdp": "<<RFC 8866 SDP>>"
},
"biz_opaque_callback_data": "0fS5cePMok"
}
Wait for the 200 OK before transmitting call media. If pre-accept was used, the WebRTC connection is already up — media flows immediately. Otherwise, give the connection time to negotiate before pushing audio.
biz_opaque_callback_data is optional. Cloud API doesn't process it; it's echoed back in the subsequent Terminate webhook for your own correlation. Up to 512 chars.
Reject the Call
{
"messaging_product": "whatsapp",
"call_id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA",
"action": "reject"
}
A reject also triggers the Call Terminate webhook with direction: USER_INITIATED and status: COMPLETED or FAILED.
Terminate
{
"messaging_product": "whatsapp",
"call_id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA",
"action": "terminate"
}
Always call terminate even if you sent an RTCP BYE — Meta uses this for accurate billing. You don't need to call it when the user hangs up; you'll just receive the Call Terminate webhook.
Call Connect Webhook (SDP Offer)
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "102290129340398",
"changes": [
{
"field": "calls",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550001234",
"phone_number_id": "123456789012345"
},
"contacts": [
{
"profile": { "name": "Pablo Morales" },
"wa_id": "12015550123"
}
],
"calls": [
{
"id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA",
"to": "15550001234",
"from": "12015550123",
"event": "connect",
"direction": "USER_INITIATED",
"timestamp": "1671644824",
"deeplink_payload": "campaign_id_42",
"cta_payload": "support_button_v2",
"session": {
"sdp_type": "offer",
"sdp": "<<RFC 8866 SDP>>"
}
}
]
}
}
]
}
]
}
Apply the SDP offer to your WebRTC stack to generate your SDP answer, then pre_accept or accept with that answer.
Call Terminate Webhook
Same envelope as BIC's terminate webhook but with direction: USER_INITIATED. See Business-Initiated → Call Terminate Webhook for the field reference. UIC terminates can also include deeplink_payload and cta_payload if the call originated from a deep link or CTA button.
deeplink_payload and cta_payload
Two arbitrary payload strings come through on UIC webhooks when the call originated from a Dualhook-instrumented entry point:
| Field | Source | Set on |
|---|---|---|
cta_payload | payload field on a voice_call interactive message or template button | When user taps the call button on a message you sent |
deeplink_payload | biz_payload query parameter on wa.me/call/<num>?biz_payload=... | When user clicks your deep link |
Both are echoed back in both the Call Connect webhook and the Call Terminate webhook. Use them to correlate the call with the campaign / page / button that drove it. Available on WhatsApp client version 2.25.27+.
DTMF (Dial Pad) Support
WhatsApp client apps include a dial pad during calls with Cloud API business numbers. Users press tone buttons; the DTMF tones are injected into the WebRTC RTP stream per RFC 4733.
| Property | Value |
|---|---|
| Clock rate | 8,000 Hz only (48,000 not supported) |
| Payload type | 126 (default if absent in your SDP) |
| Timestamp / sequence base | Same as regular audio packets |
| Webhook for DTMF digits | None — DTMF is in-band only |
For BIC, your SDP offer should include 8000 clock rate. For UIC, Meta's SDP offer already has it. The dial pad supports IVR-style flows but doesn't support consumer-to-consumer calls or initiating new calls / messages.
Common Errors
| Code | Meaning | Fix |
|---|---|---|
138000 | Calling not enabled | Configure call settings (Calling Configuration) |
138019 | Call setup failed (sent in terminate webhook) | Retry; check media path |
138020 | Relay connection failed (sent in terminate webhook) | Retry; check network |
138021 | Media receive timeout (no media for ~20–30s) | Confirm your media server is sending RTP packets |
138022 | Media transmit timeout | Verify outbound media path |
138023 | Call accepted but terminated with no media signals | Investigate WebRTC setup |
131044 | No valid payment method on user-initiated call | Attach a payment method |
131055 | Graph API call attempted on a SIP-enabled number | Use SIP signaling instead |
Full error reference on Calling Troubleshooting.