Calling API
Configure voice calling capabilities, manage call permissions, and control call sessions for WhatsApp Business.
Official Documentation: WhatsApp Calling API
Overview
The Calling API enables voice call management:
- Configure Settings: Enable/disable calling and set icon visibility
- Check Permissions: Verify if users can receive calls
- Initiate Calls: Start voice call sessions
- Manage Sessions: Accept, reject, and terminate calls
- SIP Integration: Connect with SIP infrastructure
Endpoints
POST /{PHONE_NUMBER_ID}/settingsGET /{PHONE_NUMBER_ID}/settings?fields&include_sip_credentialsGET /{PHONE_NUMBER_ID}/call_permissions?user_wa_idPOST /{PHONE_NUMBER_ID}/callsImportant Notes
Quick Start
import WhatsApp from 'meta-cloud-api';
const client = new WhatsApp({ accessToken: process.env.CLOUD_API_ACCESS_TOKEN!, phoneNumberId: Number(process.env.WA_PHONE_NUMBER_ID), businessAcctId: process.env.WA_BUSINESS_ACCOUNT_ID,});
// Enable callingawait client.calling.updateCallingSettings({ calling: { status: 'ENABLED', call_icon_visibility: 'DEFAULT', },});
// Check if user can be calledconst permissions = await client.calling.getCallPermissions({ userWaId: '15551234567',});
// Initiate a callconst call = await client.calling.initiateCall({ to: '15551234567', session: { sdp_type: 'offer', sdp: 'v=0\r\no=- 123 0 IN IP4 192.168.1.1\r\n...', },});
// Terminate callawait client.calling.terminateCall({ call_id: call.calls[0].id,});Configure Calling Settings
Enable or disable calling functionality and set icon visibility.
Enable Calling
await client.calling.updateCallingSettings({ calling: { status: 'ENABLED', call_icon_visibility: 'DEFAULT', },});
console.log('Calling enabled');Disable Calling
await client.calling.updateCallingSettings({ calling: { status: 'DISABLED', },});
console.log('Calling disabled');Hide Call Icon
await client.calling.updateCallingSettings({ calling: { status: 'ENABLED', call_icon_visibility: 'HIDDEN', },});Get Current Settings
const settings = await client.calling.getCallingSettings({ fields: ['calling'],});
console.log('Status:', settings.calling.status);console.log('Icon visibility:', settings.calling.call_icon_visibility);Get SIP Credentials
const settings = await client.calling.getCallingSettings({ fields: ['calling'], include_sip_credentials: true,});
console.log('SIP credentials:', settings.sip_credentials);Check Call Permissions
Verify if a user can receive calls from your business.
const permissions = await client.calling.getCallPermissions({ userWaId: '15551234567',});
if (permissions.can_call) { console.log('User can receive calls');} else { console.log('User cannot receive calls:', permissions.reason);}Check Multiple Users
async function checkBulkPermissions(phoneNumbers: string[]) { const results = await Promise.all( phoneNumbers.map(async number => { const permissions = await client.calling.getCallPermissions({ userWaId: number, }); return { number, canCall: permissions.can_call }; }) );
return results;}
const permissions = await checkBulkPermissions([ '15551234567', '15559876543',]);Initiate Calls
Start a voice call session with a user.
Basic Call
const call = await client.calling.initiateCall({ to: '15551234567', session: { sdp_type: 'offer', sdp: generateSDP(), // From your SIP provider },});
console.log('Call ID:', call.calls[0].id);Call with Tracking Data
const call = await client.calling.initiateCall({ to: '15551234567', session: { sdp_type: 'offer', sdp: generateSDP(), }, biz_opaque_callback_data: JSON.stringify({ customerId: 'CUST123', callReason: 'support', timestamp: new Date().toISOString(), }),});Call with Error Handling
async function makeCall(phoneNumber: string) { try { // Check permissions first const permissions = await client.calling.getCallPermissions({ userWaId: phoneNumber, });
if (!permissions.can_call) { throw new Error(`Cannot call user: ${permissions.reason}`); }
// Initiate call const call = await client.calling.initiateCall({ to: phoneNumber, session: { sdp_type: 'offer', sdp: await getSipSDP(), }, });
console.log('Call initiated:', call.calls[0].id); return call; } catch (error) { console.error('Call failed:', error.message); throw error; }}Manage Call Sessions
Handle different call actions during the session lifecycle.
Accept Call
await client.calling.acceptCall({ call_id: 'CALL_ID', session: { sdp_type: 'answer', sdp: answerSDP, },});Pre-Accept Call (Early Media)
await client.calling.preAcceptCall({ call_id: 'CALL_ID', session: { sdp_type: 'answer', sdp: earlyMediaSDP, },});Reject Call
await client.calling.rejectCall({ call_id: 'CALL_ID',});Terminate Call
await client.calling.terminateCall({ call_id: 'CALL_ID',});Complete Call Flow Example
import WhatsApp from 'meta-cloud-api';
class CallManager { private client: WhatsApp; private activeCalls = new Map<string, any>();
constructor(client: WhatsApp) { this.client = client; }
async initiateCall(phoneNumber: string) { // Verify permissions const permissions = await this.client.calling.getCallPermissions({ userWaId: phoneNumber, });
if (!permissions.can_call) { throw new Error('User cannot receive calls'); }
// Get SDP from SIP provider const sdp = await this.getSipOffer();
// Start call const call = await this.client.calling.initiateCall({ to: phoneNumber, session: { sdp_type: 'offer', sdp, }, biz_opaque_callback_data: JSON.stringify({ initiatedAt: new Date().toISOString(), }), });
const callId = call.calls[0].id; this.activeCalls.set(callId, { phoneNumber, startedAt: new Date(), status: 'initiated', });
return callId; }
async acceptCall(callId: string) { const sdp = await this.getSipAnswer();
await this.client.calling.acceptCall({ call_id: callId, session: { sdp_type: 'answer', sdp, }, });
const call = this.activeCalls.get(callId); if (call) { call.status = 'connected'; call.connectedAt = new Date(); } }
async terminateCall(callId: string) { await this.client.calling.terminateCall({ call_id: callId });
const call = this.activeCalls.get(callId); if (call) { call.status = 'terminated'; call.endedAt = new Date(); call.duration = call.endedAt - call.connectedAt; }
this.activeCalls.delete(callId); }
private async getSipOffer(): Promise<string> { // Get SDP offer from your SIP provider return 'v=0\r\no=- 123 0 IN IP4 192.168.1.1\r\n...'; }
private async getSipAnswer(): Promise<string> { // Get SDP answer from your SIP provider return 'v=0\r\no=- 456 0 IN IP4 192.168.1.2\r\n...'; }}Handle Call Webhooks
Process call-related webhook events.
import { WebhookProcessor } from 'meta-cloud-api/webhook';
const webhook = new WebhookProcessor({ verifyToken: process.env.WEBHOOK_VERIFY_TOKEN!,});
webhook.on('call', async (call, metadata) => { console.log('Call event:', call);
switch (call.status) { case 'ringing': console.log('Call is ringing'); // Auto-accept or route to agent break;
case 'connected': console.log('Call connected'); // Start call timer break;
case 'ended': console.log('Call ended'); // Log call duration, update records break;
case 'failed': console.log('Call failed:', call.reason); // Handle failure, retry if needed break; }});Response Formats
Initiate Call Response
{ calls: [ { id: 'CALL_ID', from_phone_number_id: '1234567890', to: '15551234567', status: 'initiated' } ]}Call Permissions Response
{ can_call: true, reason: null // or reason if cannot call}Settings Response
{ calling: { status: 'ENABLED', call_icon_visibility: 'DEFAULT' }, sip_credentials: { username: 'sip_user', password: 'sip_pass' }}Error Handling
try { await client.calling.initiateCall({ to: '15551234567', session: { sdp_type: 'offer', sdp: 'v=0...' }, });} catch (error) { if (error.response) { const { code, message } = error.response.data.error;
switch (code) { case 131053: console.error('User cannot receive calls'); break; case 131054: console.error('Invalid SDP data'); break; case 131000: console.error('Calling not enabled'); break; default: console.error(`Error ${code}: ${message}`); } }}Best Practices
-
Check Permissions First: Verify before calling
const permissions = await client.calling.getCallPermissions({userWaId: phoneNumber,});if (permissions.can_call) {await client.calling.initiateCall({ to: phoneNumber, ... });} -
Use Tracking Data: Attach metadata to calls
const trackingData = {customerId: 'CUST123',agentId: 'AGT456',department: 'support',};await client.calling.initiateCall({to: phoneNumber,biz_opaque_callback_data: JSON.stringify(trackingData),session: { ... },}); -
Handle SIP Integration Properly: Use reliable SIP providers
class SipProvider {async createOffer(): Promise<string> {// Generate SDP offer with proper codec supportreturn 'v=0\r\no=- ...\r\n';}async createAnswer(offer: string): Promise<string> {// Generate SDP answerreturn 'v=0\r\no=- ...\r\n';}} -
Implement Call Logging: Track all call activities
interface CallLog {callId: string;to: string;initiatedAt: Date;connectedAt?: Date;endedAt?: Date;duration?: number;status: string;}async function logCall(log: CallLog) {await database.callLogs.insert(log);} -
Handle Failures Gracefully: Implement retry logic
async function makeCallWithRetry(phoneNumber: string,maxRetries: number = 3) {for (let i = 0; i < maxRetries; i++) {try {return await client.calling.initiateCall({to: phoneNumber,session: await getSipSession(),});} catch (error) {if (i === maxRetries - 1) throw error;await delay(1000 * (i + 1)); // Exponential backoff}}}
SIP Integration
WhatsApp calling requires SIP infrastructure. Here’s a basic overview:
interface SipSession { sdp_type: 'offer' | 'answer'; sdp: string;}
// SDP (Session Description Protocol) exampleconst sdpOffer = `v=0o=- 123456789 123456789 IN IP4 192.168.1.1s=WhatsApp Callc=IN IP4 192.168.1.1t=0 0m=audio 49170 RTP/AVP 0 8a=rtpmap:0 PCMU/8000a=rtpmap:8 PCMA/8000`;Recommended SIP Providers
- Twilio
- Vonage (Nexmo)
- Bandwidth
- Plivo
Related Documentation
- Phone Numbers API - Manage phone numbers
- Webhooks - Handle call events
- Messages API - Send messages
- SIP Integration Guide - Detailed SIP setup
Source Code
View the source code on GitHub: CallingApi.ts