Skip to main content
Control WhatsApp calls from your server with messaging.voip. To speak and listen on a call from server code, use CallsClient from @polymorfa/sdk/calls. For browser calling, see the Calls guide.

Place a call

session is the Number that places the call. participant names who acts on the call for your server, as server:agent-7. It defaults to default. Set video: true for a video call. Pass an idempotency key so the SDK can retry the request without placing a second call.

Check before you call

check runs the same checks as place without placing a call. refusal is calls_disabled, call_recipient_opted_out, call_destination_blocked, call_permission_required, or call_limit_reached. place runs the checks again, so a call can still fail if something changed in between.

Answer, decline, and end calls

Incoming calls arrive as call.received webhooks. A call rings until someone answers or declines it. Nothing answers automatically.
  • accept answers a ringing call. Later accepts by other participants join it, unless the first participant set exclusive: true. Then they fail with 409 call_claimed.
  • reject declines a ringing call. It fails with 409 call_not_ringing otherwise.
  • end ends the call for everyone. leave(callId, { connectionId }) closes one of your connections and leaves the call running.
  • addParticipant(callId, { to }) invites another WhatsApp user.

Ask for call permission

WhatsApp requires a person’s permission before an Official API Number calls them. Read the current state, then ask with a message:
WhatsApp allows one request per person every 24 hours and two every 7 days. A request over the limit fails with call_permission_request_limited. Changes arrive as call.permission_changed webhooks. Linked-device Numbers do not use call permission.

Change call settings for a Number

An update changes only the fields you send.
  • callsEnabled: false turns calling off for the Number. New calls fail with calls_disabled and incoming calls are declined. Calls in progress continue.
  • conferenceMode (default true) lets every participant you connect hear each other. With false, each hears only the WhatsApp caller.
  • inboundRoute: "sip_trunk" with sipTrunkId sends incoming calls to a SIP trunk. clients (the default) rings your apps and browsers.
A stale expectedRevision fails with 409 state_conflict.

Speak on a call from server code

CallsClient follows one Number’s calls and gives you each call’s audio, for example to run a voice agent:
call.audio carries signed 16-bit mono PCM at call.audio.sampleRate. Write your own audio with call.audio.write(). The client reconnects after a dropped connection. Call disconnect() to stop following the Number.

Connect a SIP trunk

A SIP trunk connects your PBX to a project’s calls. Read the address to configure in your PBX:
Then create the trunk on a project client:
Disable a trunk with update(trunkId, { enabled: false, expectedRevision }). rotateCredentials(trunkId) issues a new inbound password. A team client passes the project ID as the first argument to list and create. See SIP trunks.

Block countries and keep a do-not-call list

The team’s call policy blocks calls to country calling codes. It needs a team key:
update replaces the whole list. Codes are 1 to 4 digits without +; 1876 blocks Jamaica without blocking the rest of +1. While any code is blocked, calls to people whose phone number is unknown are refused too. The do-not-call list blocks calls to specific people:
A call to a listed person fails with call_recipient_opted_out. Matching uses the identifier you stored: a phone number entry does not block a call addressed by user ID. import({ entries }) adds up to 5,000 entries at once. For call history and statistics, see Call records.