What This Page Covers
A consolidated reference for the Calling API: every endpoint, every webhook, and the media-path guidelines you need to build a working WebRTC integration. For task-oriented walkthroughs see Business-Initiated Calls, User-Initiated Calls, Configuration, Permissions, and SIP.
Endpoints Summary
All calling endpoints are scoped to a business phone number.
| Method + Endpoint | Purpose |
|---|---|
POST /<PHONE_NUMBER_ID>/settings | Configure call settings (status, call_hours, callback_permission_status, audio codecs, SIP, SRTP key exchange) |
GET /<PHONE_NUMBER_ID>/settings | Read current settings. Add ?include_sip_credentials=true to include SIP password |
POST /<PHONE_NUMBER_ID>/calls | All call actions: connect (BIC), pre_accept/accept/reject (UIC), terminate (both) |
GET /<PHONE_NUMBER_ID>/call_permissions?user_wa_id=<USER_WHATSAPP_ID> | Get call permission state — current status, expiration, action limits |
POST /<PHONE_NUMBER_ID>/messages (interactive voice_call) | Send a free-form call button message |
POST /<PHONE_NUMBER_ID>/messages (interactive call_permission_request) | Send a free-form permission request |
POST /<WABA_ID>/message_templates (with voice_call button) | Create a call-button template |
POST /<WABA_ID>/message_templates (with call_permission_request component) | Create a permission-request template |
GET /<WABA_ID>/?fields=call_analytics | Get cost, completed-call counts, average call duration (Pricing) |
POST /<PHONE_NUMBER_ID>/calls carries different payloads depending on action. Common shape:
{
"messaging_product": "whatsapp",
"to": "12015550123", // BIC only
"call_id": "wacid.HBgL...", // pre_accept/accept/reject/terminate
"action": "connect | pre_accept | accept | reject | terminate",
"session": {
"sdp_type": "offer | answer",
"sdp": "<<RFC 8866 SDP>>"
},
"biz_opaque_callback_data": "0fS5cePMok" // optional
}
session is required on connect (offer), pre_accept (answer), accept (answer). Not used on reject or terminate.
Webhooks Summary
Subscribe to the calls webhook field to receive these. In Dualhook setups they're delivered directly to your endpoint via Webhook Override — Dualhook does not ingest them.
| Webhook | When | Notes |
|---|---|---|
| Call Connect (UIC) | User initiates a call | Contains SDP offer to apply to your WebRTC stack |
| Call Connect (BIC) | After your POST /calls connect succeeds | Contains SDP answer from Meta |
| Call Status (BIC) | RINGING, ACCEPTED, REJECTED | ACCEPTED typically arrives after media is already established — for auditing |
| Call Terminate | Either party hangs up, you call terminate/reject, or call fails | Includes start_time, end_time, duration if call was answered. errors[] if it failed. |
User Call Permission Reply (messages field) | User accepts, rejects, or auto-revokes permission | Carries is_permanent, expiration_timestamp, response_source |
Note: the user permission reply arrives on the messages field (not calls) because it's a user response to your message. Subscribe to both fields.
The account_settings_update webhook field tracks changes to calling.status, calling.call_icon_visibility, calling.callback_permission_status, calling.sip.status, and calling.srtp_key_exchange_protocol. See Configuration → Settings Webhook.
Media Path Guidelines
Mandatory for all media — failures here either cause call failure during signaling or media decoding:
- OPUS audio codec only (or PCMA/PCMU if explicitly enabled in
audio.additional_codecsfor transcoding interop). - Media clock rate: 48 kHz.
- DTMF clock rate: 8 kHz (only).
- ptime: 20ms.
- Single audio SSRC. Meta's relay overwrites your business audio SSRC to a fixed value before relaying to the WhatsApp client. Multiple SSRCs cause undefined behavior — corruption, glitches, total media failure.
Recommended (not mandatory but produces good call quality):
- ECDSA certificates for DTLS (faster cert generation, shorter handshake, no fragmentation).
- Pre-accept (UIC) to frontload the WebRTC connection before media flows.
ICE and WebRTC
Meta's WebRTC stack is ICE-LITE. Your stack should be ICE-FULL to maintain compatibility:
- Initiate ICE connectivity checks via STUN.
- Take the CONTROLLING role. Meta only takes CONTROLLED. Starting CONTROLLED can stall and timeout, or take longer due to role-conflict resolution.
- Use regular nomination, not aggressive nomination (RFC 5245).
- Wait for the ICE process to complete before nominating the candidate and starting DTLS.
- Don't switch the candidate mid-call.
ICE Trickle is supported — Meta will accept STUN connectivity checks from addresses not included in your initial SDP, as long as STUN message integrity passes.
Meta does not provide STUN/TURN servers and has no recommendations on third-party providers. If your media terminates in your own infra (typical for IVR/contact center setups), STUN/TURN often isn't needed — your VIP behind a load balancer can serve as the ICE candidate.
DTLS Certificates
- Use ECDH keys to prevent packet fragmentation during handshake transmission.
- Your business should act as the DTLS client (RFC 6347 Section 4.2).
- Common DTLS retry intervals (1s, 2s, 4s) cause ~3 seconds of delayed setup after Meta receives your SDP, which can result in audio clipping. Switch to SDES key exchange to avoid this — see Calling Troubleshooting → Audio Clipping.
SDP Overview
Session Description Protocol describes media parameters for WebRTC negotiation: codecs, ICE candidates, DTLS fingerprints, etc. Your session.sdp field on POST /calls must be RFC 8866 compliant.
Key requirements:
- Single stream per SDP. Multiple streams not supported.
- One audio track per stream.
- CRLF (
\r\n) line endings within the SDP string. Don't double-serialize —session.sdpis just a string field. - DTMF payload type 126 with 8000 clock rate (default if absent).
a=fingerprintline when using DTLS for SRTP key exchange. Without it, you'll get "No fingerprint found in SDP" — switch to SDES if you can't include a fingerprint.
For a business agent's browser to work in UIC, the WebRTC agent in the browser should generate an SDP answer (not offer). Pass that answer to POST /calls accept.
Meta cannot work with any SDP offer other than the one it generates and provides on the UIC connect webhook.
Media Relay Targeting (Latency)
Meta's targeting algorithm picks the Meta relay closest to the WhatsApp consumer. That relay's IP is the ICE candidate Meta sends in the SDP.
On your side, place media servers in the same region as the consumer to minimize latency. For international calls, this minimizes the public-internet routing length.
Optimize against the candidate IPs on Meta's SDPs, not against the source of signaling endpoints (which can be different).
Bandwidth Budget
Per call:
| Codec | Bandwidth |
|---|---|
| OPUS | ~40 kbps codec + ~20 kbps overhead = ~60 kbps total |
| G.711 (PCMA/PCMU) | 64 kbps codec + 20 kbps overhead = ~84 kbps total |
Multiply by concurrent calls. A 1 Mbps link supports roughly:
- ~15 concurrent OPUS calls
- ~12 concurrent G.711 calls
OPUS is variable-rate based on network conditions. A 1-minute OPUS call uses ~3.75 MB; G.711 uses ~4.9 MB.
Maximum concurrent calls per phone number: 1,000.
Webhook Delivery Semantics
- No ordering guarantees for calling webhooks. Terminate can arrive before connect if the user hangs up immediately. Connect retries can interleave with terminate sends. Use the
timestampfield to order events. - No exactly-once delivery. Be prepared for duplicates. Common causes: 20-second HTTP timeout from Meta's side (your endpoint thinks it succeeded; Meta retries), multiple apps subscribed to
calls(one app fails, Meta retries the whole dispatch hitting the others again), Meta queue infrastructure recovery. - Retry policy is shorter than messaging webhooks (which retry up to 7 days). Meta hasn't published exact numbers — assume stale webhooks may still arrive and check
timestampbefore acting. - Multiple webhook endpoints (one app per URL) get all calling webhooks dispatched to all of them. No notion of primary/secondary — all are equal.
- Different URLs for messaging vs calling is achievable by using two separate Meta apps (one subscribed to
messages, one tocalls) and overriding callback URLs per app. Or just use one app and one URL — distinguish by payload field (field: "calls"vsfield: "messages").
IP Allowlisting
Meta's webhook servers come from a fluid set of IP ranges. As of mid-2024, this collapses to ~23 IPv4 prefixes (full list updates over time):
31.13.24.0/21 31.13.64.0/18 45.64.40.0/22
57.141.0.0/21 57.141.8.0/22 57.141.12.0/23
57.144.0.0/14 66.220.144.0/20 69.63.176.0/20
69.171.224.0/19 74.119.76.0/22 102.132.96.0/20
103.4.96.0/22 129.134.0.0/16 147.75.208.0/20
157.240.0.0/16 163.70.128.0/17 163.77.128.0/17
173.252.64.0/18 179.60.192.0/22 185.60.216.0/22
185.89.216.0/22 204.15.20.0/22
Generate the current list yourself:
whois -h whois.radb.net -- '-i origin AS32934' | grep '^route' | awk '{print $2}' | sort
Or use the geofeed CSV from Meta's webhooks documentation. Better: use mTLS for webhook authentication instead of IP allowlisting, since the IPs do change. Meta supports mTLS for webhooks (separate from SIP, which doesn't support mTLS).
For SIP-mode setups, the same Meta IP space applies — your SIP server's firewall must allow inbound TLS from these prefixes.