Business-initiated call flow
Updated: Apr 2, 2026
Copy for LLM
This walkthrough shows a complete business-initiated call flow, from requesting permission to ending the call.
Before you begin
- Complete the setup described in the Calling overview
- Subscribe to the
callsandcall_permission_replywebhook fields
Send a call permission request
Send the consumer a call permission request:
POST /{page-id}/messages { "recipient": { "id": "{psid}" }, "message": { "attachment" : { "type":"template", "payload":{ "template_type":"calling_optin" } } } }
You receive a confirmation response:
{ "recipient": { "id": "{psid}" }, "message_id": "{mid}" }
Receive the permission reply webhook
When the consumer approves or rejects the request, you receive a
call_permission_reply webhook:{ "object": "page", "entry": [ { "sender": { "id": "{psid}" }, "recipient": { "id": "{page-id}" }, "timestamp": 1671644824, "call_permission_reply": { "response": "[approve|reject]", "expiration_timestamp": "{timestamp}" } } ] }
Initiate the call
Generate an SDP offer and call the
/calls API:const offer = await pc.createOffer(); await pc.setLocalDescription(offer);
POST /{page-id}/calls { "platform": "messenger", "to": "{psid}", "action": "connect", "session": { "sdp_type": "offer", "sdp": "<<RFC 4566 SDP>>" } }
Receive call status webhooks
You receive webhooks as the call progresses:
{ "object": "page", "entry": [ { "id": "{page-id}", "time": 1671644824, "calls": [ { "id": "c_M5YPhGE5jm0L_PMQdScLlYJlR7tU_f0rgXiTQ", "event": "call_status", "timestamp": 1671644824, "recipient_id": "{psid}", "call_status": "[ringing | accepted]" } ] } ] }
Handle the media update webhook
After the consumer accepts, you receive a
media_update webhook with an SDP renegotiation offer:{ "object": "page", "entry": [ { "id": "{page-id}", "time": 1671644824, "calls": [ { "id": "c_M5YPhGE5jm0L_PMQdScLlYJlR7tU_f0rgXiTQ", "event": "media_update", "timestamp": 1671644824, "session": { "version": 1, "sdp_renegotiation": { "sdp_type": "offer", "sdp": "<<RFC 4566 SDP>>" } } } ] } ] }
Generate an SDP answer and apply it to your peer connection:
onMediaUpdate = async (mediaUpdateSDP) => { pc.setRemoteDescription(new RTCSessionDescription({ sdp: mediaUpdateSDP.sdp, type: mediaUpdateSDP.sdp_type })).then( () => { pc.createAnswer().then(answer => { pc.setLocalDescription(answer); }); } ) }
The call is now connected. You can hang up at any time, or receive a terminate webhook when the consumer ends the call.