WhatsApp

WhatsApp calling

Turn on WhatsApp voice calls for a number, route them to a phone, SIP endpoint or AI agent, ask customers for permission and place outbound calls.

With calling turned on, customers can tap the call button in your WhatsApp chat and reach you, and you can call customers who have said yes. Pozy bridges every call to a destination you choose: a phone line, a SIP endpoint such as an AI voice agent, or a WebSocket media server you run.

#What you need

Requirement Detail
A Cloud API-only number Calling is off for numbers in coexistence with the WhatsApp Business app
Usage-based billing on the workspace Calls meter per minute (Pricing)
A messaging limit of about 2,000 daily recipients or more Meta's threshold for calling. A new number starts at TIER_250; send normal traffic until Meta raises it.
A country where Meta allows business-initiated calls Meta blocks outbound calls from numbers registered in the US, Canada, Egypt, Vietnam and Nigeria. Inbound calls to those numbers still work.

If a requirement is missing, enabling calling returns 422.

#Where a call can go

forwardTo takes one of three kinds of address:

Prefix Example Use it for
tel: tel:+351210000123 A desk phone or a mobile
sip: sip:agent@voice.example.com A PBX, a contact centre or an AI voice agent with a SIP address
wss: wss://media.example.com/calls Your own media server. Closing the socket ends the call.

Calls to a sip: or wss: destination have no phone-network leg, so there is no carrier charge for that leg.

#Turn calling on

#Read the current config

GET /v1/whatsapp/calling?accountId=... returns phoneNumberDocId, the number id every other calling call takes, plus the current destination, recording flag and callDeepLink.

#Enable it with a destination

Call POST /v1/phone-numbers/{id}/whatsapp/calling with the account and forwardTo.

curl -X POST "https://api.pozy.ai/v1/phone-numbers/6715d2a7f3b6e81c4d0a5b97/whatsapp/calling" \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "6712a9d4c0e3b58f1a7d2e64",
    "forwardTo": "sip:frontdesk@voice.example.com",
    "recordingEnabled": false,
    "maxCallDurationSeconds": 1800
  }'

Once calling is on, callDeepLink in the config is a public https://wa.me/call/... link. Put it on your website, in emails or behind a QR code and it starts a WhatsApp call to the number.

Options on enable and on PATCH:

Field Default Effect
recordingEnabled false Records calls. A consent prompt plays first, and a per-minute recording charge applies.
maxCallDurationSeconds none Hangs up after this many seconds (up to 14,400) so a destination that never hangs up cannot run up minutes
forwardCallerId business caller shows the customer's number to a sip: destination instead of yours
sipAuthUsername, sipAuthPassword none Credentials if your SIP destination requires them
callIconCountries none Two-letter country codes where WhatsApp shows the call button in the chat

DELETE turns calling off but keeps the destination and credentials, so turning it back on needs no setup.

#Call a customer

WhatsApp requires the customer's permission before a business calls them.

#Check permission

GET /v1/whatsapp/call-permissions?accountId=...&to=... returns the permission status (permanent, temporary with an expiration_time, or no_permission) and which actions are allowed right now.

Response
{
  "permission": { "status": "no_permission" },
  "actions": [
    { "action_name": "send_call_permission_request", "can_perform_action": true },
    { "action_name": "start_call", "can_perform_action": false }
  ]
}

#Ask for permission if needed

Send the request with POST /v1/whatsapp/calls and action: "send_call_permission_request", optionally with your own bodyText (up to 1,024 characters). The customer taps Allow in the chat. Their answer arrives as call.permission_request.

Meta allows one request per customer per 24 hours and two per 7 days, and only while a 24-hour service window is open.

#Place the call

When start_call is allowed, call POST /v1/whatsapp/calls with to. The call bridges to the number's forwardTo, or to a forwardTo you pass on this call.

curl -X POST https://api.pozy.ai/v1/whatsapp/calls \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: callback-order-4821" \
  -d '{
    "accountId": "6712a9d4c0e3b58f1a7d2e64",
    "to": "+351912345678"
  }'

Always send an Idempotency-Key. A retry after a timeout then returns the first response instead of dialling, and billing, a second call.

To hang up, ringing or answered, inbound or outbound, call POST /v1/voice/calls/{id}/end with the callId (for inbound calls, the call id on call.received).

#Estimate and review costs

Before dialling, GET /v1/whatsapp/calls/estimate with to and minutes returns the per-minute price and a breakdown: Meta's fee, the connection charge and recording if you ask for it.

GET /v1/whatsapp/calls lists past calls, filtered by status, direction, since and until, up to 200 per page. Pass nextCursor back as before for the next page. Each call has its duration, endReason (hangup, no_answer, rejected, error), a recordingUrl when recording was on, and billing:

Field Meaning
billing.billableCostUSD What Pozy bills for the call
billing.recordingCostUSD The recording part of that amount
billing.metaCostUSD Meta's own per-minute fee, billed by Meta to your WABA. Shown for reference. Always 0 on inbound calls.
billing.totalCostUSD Both together

Fetch a recording file with GET /v1/whatsapp/calls/{id}/recording.

#Caller ID on numbers you brought yourself

When a call is forwarded to a tel: destination, the phone that rings shows a caller ID. A number bought through Pozy shows itself straight away. A number you brought from your own carrier cannot until it is verified, because carriers block unverified caller IDs; until then the forwarded call shows a Pozy number and the config reads callerIdMode: "platform".

To verify it:

  1. Call POST /v1/phone-numbers/{id}/whatsapp/caller-id-verification. The number receives a one-time code by SMS or voice call. Repeat the call to resend.
  2. Submit the code with POST .../caller-id-verification/verify.

After that, callerIdMode is business and callerIdVerified is true. Numbers bought through Pozy never need this step and get a 400 if you try.

#Common errors

Status Cause Fix
409 on POST /v1/whatsapp/calls No active permission from the customer Check permission and send a request first
422 on enable Billing, messaging tier or country rule not met See What you need
422 on a call Calling is off on the number, the country blocks outbound calls, or SIP credentials are missing Enable calling or fix the destination
502 on a call The call could not be connected and is marked failed Try again later with a new idempotency key

#Endpoints

Endpoint What it does
GET /v1/whatsapp/calling Get calling config for an account
POST /v1/whatsapp/phone-numbers/{id}/calling Enable calling on a number
PATCH /v1/whatsapp/phone-numbers/{id}/calling Update calling config
DELETE /v1/whatsapp/phone-numbers/{id}/calling Disable calling on a number
GET /v1/whatsapp/call-permissions Check call permission
GET /v1/whatsapp/calls List call history for an account
POST /v1/whatsapp/calls Initiate outbound call
GET /v1/whatsapp/calls/{id} Get a single call
GET /v1/whatsapp/calls/{id}/recording Get a call recording
GET /v1/whatsapp/calls/estimate Estimate per-minute cost
GET /v1/phone-numbers/{id}/whatsapp/calling Get calling config for a number
POST /v1/phone-numbers/{id}/whatsapp/calling Enable calling on a number
PATCH /v1/phone-numbers/{id}/whatsapp/calling Update calling config
DELETE /v1/phone-numbers/{id}/whatsapp/calling Disable calling on a number
POST /v1/phone-numbers/{id}/whatsapp/caller-id-verification Start caller-ID verification for a customer-brought number
POST /v1/phone-numbers/{id}/whatsapp/caller-id-verification/verify Confirm the caller-ID verification code

#Webhook events

Event When it fires
call.received Call received.
call.ended Call ended.
call.failed Call failed.
call.permission_request Call permission request reply.