Skip to content

Voice Calls

How WhatsApp calling differs from a normal dialler

Section titled “How WhatsApp calling differs from a normal dialler”

You cannot simply dial a customer. WhatsApp requires the customer to accept a call permission request before your business can call them, and Meta enforces how often you may ask:

RuleLimit
Permission requests per contact1 per 24 hours, 2 per week
Calling window once accepted7 days
Consecutive unanswered calls before permission is revoked4

Inbound (customer-initiated) calls bypass all of this — they are free and need no permission (except from the countries listed under Geo restrictions).

cURL
curl -X POST https://api.whatbot.in/public/v1/calls/permission-request \
-H "X-API-Key: wa_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "wa_id": "918610173741" }'

The contact receives a prompt in WhatsApp asking whether your business may call them. Their answer arrives asynchronously — subscribe to call.permission.updated (see Webhooks) or poll the endpoint below.

Response

{
"wa_id": "918610173741",
"status": "requested",
"requested_at": "2026-08-18T12:04:11+00:00",
"request_sent": true,
"can_call": false,
"can_call_reason": "permission not granted (status=requested)",
"can_request": false,
"can_request_reason": "limit reached: 1 request per 24h"
}

This is the same shape GET /permission returns, plus request_sent. The can_* fields are computed from the row and the clock rather than stored, so they answer “may I act right now” without you having to re-derive the cadence and window rules.

cURL
curl https://api.whatbot.in/public/v1/calls/permission/918610173741 \
-H "X-API-Key: wa_live_xxx"
{
"wa_id": "918610173741",
"status": "approved",
"expires_at": "2026-08-25T12:09:02+00:00",
"can_call": true,
"can_call_reason": "",
"can_request": false,
"can_request_reason": "already approved — calling window is still open"
}

status is one of none, requested, approved, rejected, expired, revoked. Always branch on can_call rather than re-deriving the rules yourself.

cURL
curl -X POST https://api.whatbot.in/public/v1/calls \
-H "X-API-Key: wa_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"wa_id": "918610173741",
"sdp": "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\n..."
}'

sdp is a WebRTC offer generated by your own media stack. WhatsApp calling uses WebRTC (ICE + DTLS + SRTP, OPUS by default); Whatbot relays the signalling but never terminates the audio, so the offer must come from the endpoint that will carry the call.

Returns 403 with the reason when permission is missing or expired, and 422 when the destination is geo-restricted. The call goes out from the business number of the contact’s most recent conversation, or the workspace’s default number if there is none.

200
{
"id": "0f6b...",
"wa_call_id": "wacid...",
"wa_id": "918610173741",
"direction": "outbound",
"status": "ringing",
"started_at": "2026-08-18T12:10:00+00:00",
"dest_country": null,
"conversation_id": "c9e6f0f7-..."
}

What happens next — answered, missed, ended — arrives on the call.* webhooks and in the call log.

cURL
curl "https://api.whatbot.in/public/v1/calls?direction=outbound&limit=20" \
-H "X-API-Key: wa_live_xxx"
{
"calls": [
{
"id": "0f6b...",
"wa_id": "918610173741",
"direction": "outbound",
"status": "completed",
"started_at": "2026-08-18T12:10:00+00:00",
"answered_at": "2026-08-18T12:10:06+00:00",
"ended_at": "2026-08-18T12:11:02+00:00",
"duration_sec": 56,
"billable_sec": 60
}
]
}
FieldTypeRequiredDescription
directionstringinbound or outbound.
statusstringringing, connected, completed, missed, rejected or failed.
conversation_idstringOnly calls on this conversation.
limitnumber1–200. Defaults to 50.
offsetnumberSkip this many, for paging. Defaults to 0.

Newest first. Each call carries its conversation_id when one exists, and the contact’s contact_name when known. wa_id is masked if your workspace masks the public API.

GET /calls/{call_id} returns one call by its id, or 404.

WhatsApp bills answered outbound calls only, rounded up to 6-second increments. Ringing and unanswered calls are free, and inbound calls are always free. billable_sec is the rounded figure — a 56-second call bills as 60 — and is 0 for anything free.

DirectionRestriction
InboundUnavailable for numbers in Cuba, Iran, North Korea, Syria
OutboundBlocked when your business number originates in US, Canada, Egypt, Vietnam, Nigeria

Note the outbound rule keys off your number’s country, not the customer’s. Blocked attempts return 422 before any call is placed.

Subscribe via Webhooks — these are opt-in and not included in the default event set:

EventFires when
call.permission.requestedYou sent a permission request
call.permission.updatedThe contact accepted or declined, or a stale permission was reconciled
call.startedA customer’s call to you started ringing (inbound only)
call.completedAn answered call ended
call.missedA call ended unanswered, declined or failed

Payloads are listed on the Webhooks page.

StatusCause
403POST /calls without an approved, unexpired permission
404GET /calls/{call_id} for a call that isn’t in your workspace
409Calling was revoked after four unanswered calls and WhatsApp still permits it (permission request)
422Invalid wa_id; geo-restricted number; the 24-hour window is closed (permission request)
429Permission-request limit reached, or the contact has already approved you
502WhatsApp rejected the request or the call