Zernio
Zernio
PlatformsWhatsAppConnection & SetupBroadcastsTemplatesContacts & ProfilePhone NumbersCallingSandboxGroup ChatsInboxMeta Business AgentFlowsClick-to-WhatsApp AdsPricing & CostsMedia & Limits
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
WhatsApp

Calling

Turn on WhatsApp Business Calling for a connected number, forward calls to a phone, SIP endpoint or AI voice agent, and place outbound calls with the customer's permission.


When you finish this page customers can call your WhatsApp number from inside the chat and the call rings wherever you chose, and you can place calls to customers who allowed it. You need a connected WhatsApp account with usage-based billing active. Zernio bridges each call to a forwardTo destination: a phone number (tel:), a SIP endpoint (sip:) or a WebSocket media server for an AI voice agent such as Vapi or Retell (wss:). Calling runs on /v1/phone-numbers/{id}/whatsapp/calling; the /v1/whatsapp/phone-numbers/{id}/calling paths are deprecated aliases.

Meta blocks business-initiated calls on numbers registered in the US, Canada, Egypt, Vietnam and Nigeria; inbound calls to those numbers still work. Elsewhere, Meta's production guidance requires the number to be at the 2,000-recipient daily messaging tier (TIER_2K); enabling calling on a TIER_250 number returns 422, and below the threshold Meta may rate-limit outbound calls.

Inbound calls are free. For outbound, Zernio meters the carrier connection per minute plus $0.004/minute when recording is on, and Meta bills its own per-minute rate directly to your WABA (WhatsApp rates). GET /v1/whatsapp/calls/estimate with accountId, to and minutes returns both legs before you dial.

Step 1: Read the calling config

Call GET /v1/whatsapp/calling with accountId. The response carries phoneNumberDocId, the {id} every other calling call takes, plus the current destination and recording flag.

import Zernio from '@zernio/node';

const zernio = new Zernio();
const accountId = '66b2e19d8c3f5a7e9d0b1c2d';

const { data: config } = await zernio.whatsappcalling.getWhatsAppCallingConfig({
  query: { accountId }
});

const phoneNumberId = config.phoneNumberDocId;

Response (200):

{
  "phoneNumberDocId": "66f6c3d4e5f6a7b8c9d0e1f3",
  "phoneNumber": "+13105551234",
  "callingEnabled": false,
  "callDeepLink": null,
  "forwardTo": null,
  "recordingEnabled": false,
  "sipAuthUsername": null,
  "sipAuthPasswordConfigured": false,
  "callerIdMode": "business",
  "callerIdVerified": true,
  "maxCallDurationSeconds": null,
  "forwardCallerId": "business"
}

Step 2: Enable calling

Call POST /v1/phone-numbers/{id}/whatsapp/calling with accountId and forwardTo. Zernio sets calling.status=ENABLED on Meta, stores the Meta-issued SIP password encrypted, and snapshots the destination.

const { data: enabled } = await zernio.whatsappcalling.enableWhatsAppCalling({
  path: { id: phoneNumberId },
  body: {
    accountId,
    forwardTo: 'tel:+13105559876',
    recordingEnabled: false
  }
});

console.log(enabled.callingEnabled);

Response (200):

{
  "success": true,
  "callingEnabled": true,
  "forwardTo": "tel:+13105559876",
  "callerIdMode": "business"
}

Recording is off by default. With recordingEnabled: true a consent prompt plays before recording starts and the per-minute surcharge applies. maxCallDurationSeconds caps a forwarded call so a destination that never hangs up cannot bill dead air, and forwardCallerId: "caller" presents the WhatsApp user's number to a sip: destination instead of yours. After enabling, callDeepLink on the config is a public https://wa.me/call/... link that starts a WhatsApp call to the number from a website, email or QR code.

Change the destination or the recording flag later with PATCH /v1/phone-numbers/{id}/whatsapp/calling and the same body shape; turn calling off with DELETE /v1/phone-numbers/{id}/whatsapp/calling?accountId=.... Disabling keeps forwardTo and the SIP credentials, so re-enabling needs no reconfiguration.

Step 3: Check call permission

WhatsApp requires the customer to allow calls before you dial them. Call GET /v1/whatsapp/call-permissions with accountId and to.

const { data: permission } = await zernio.whatsappcalling.getWhatsAppCallPermissions({
  query: { accountId, to: '+13105551234' }
});

console.log(permission.permission.status);

Response (200):

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

status is permanent, temporary (with expiration_time in Unix seconds) or no_permission. When start_call is not allowed, send the consent prompt with POST /v1/whatsapp/calls and action: "send_call_permission_request" (optionally bodyText); the customer taps Allow in WhatsApp. Meta limits the prompt to 1 per customer per 24 hours and 2 per 7 days, and it needs an open 24-hour service window.

Step 4: Place an outbound call

Call POST /v1/whatsapp/calls with accountId and to. The call bridges to the number's forwardTo unless you pass a per-call forwardTo override. Send an Idempotency-Key header so a retry replays the response instead of dialing, and billing, a second call.

const { data: call } = await zernio.whatsappcalling.initiateWhatsAppCall({
  body: { accountId, to: '+13105551234' }
});

console.log(call.callId, call.status);

Response (200):

{
  "success": true,
  "callId": "66f8e5f6a7b8c9d0e1f2a3b5",
  "status": "dialing",
  "direction": "outbound",
  "to": "+13105551234",
  "forwardTo": "tel:+13105559876",
  "recordingEnabled": false
}

The rest of the call's life arrives on the call webhooks.

Step 5: Read call history

Call GET /v1/whatsapp/calls with accountId, optionally filtered by status, direction, since and until. Each call carries its duration, end reason, recording URL when recording was on, and a billing breakdown. Pass the returned nextCursor as before for the next page.

const { data: history } = await zernio.whatsappcalling.listWhatsAppCalls({
  query: { accountId, direction: 'inbound', limit: 50 }
});

for (const c of history.calls) {
  console.log(c.direction, c.status, c.durationSeconds, c.billing?.billableCostUSD);
}

Response (200):

{
  "calls": [
    {
      "_id": "66f8e5f6a7b8c9d0e1f2a3b5",
      "direction": "inbound",
      "from": "+13105551234",
      "to": "+13105551234",
      "status": "ended",
      "startedAt": "2027-01-01T09:00:00Z",
      "endedAt": "2027-01-01T09:04:12Z",
      "durationSeconds": 252,
      "endReason": "hangup",
      "billing": {
        "metaCostUSD": 0,
        "telnyxCostUSD": 0.0336,
        "recordingCostUSD": 0,
        "billableCostUSD": 0.0336,
        "totalCostUSD": 0.0336,
        "currency": "USD"
      }
    }
  ],
  "nextCursor": null
}

billing.billableCostUSD is what Zernio bills: the carrier connection plus recording. billing.metaCostUSD is Meta's per-minute fee, billed by Meta directly to your WABA and shown for reference only. Inbound calls carry no charge on either side.

If it fails

A 409 from POST /v1/whatsapp/calls means the customer has not allowed calls:

{
  "error": "No active call permission. Send a permission request first."
}

Run Step 3, send the permission request, and dial once start_call is allowed. A 422 means calling is not enabled on the number, the number's country blocks business-initiated calls, or the Meta SIP credentials are missing; a 502 means the carrier could not originate the call and it was marked failed. Every error uses the envelope in error handling.

Related

  • WhatsApp rates: Meta's per-minute rate by country and the recording surcharge.
  • Calling API: every endpoint, including caller-ID verification and recordings.
  • Call webhooks: ringing, answered, ended and failed events.
  • Voice and calls: PSTN calling on the same numbers, and the AI agent integrations.
  • Phone numbers: which numbers can carry calling.
Was this page helpful?

Phone Numbers

Which phone number gets WhatsApp, how to read its live status from Meta, the liveness check, and registering a number that has a two-step PIN.

Sandbox

Test WhatsApp sending, replies and webhooks against Zernio's shared sandbox number before buying a number of your own.

On this page

Step 1: Read the calling configStep 2: Enable callingStep 3: Check call permissionStep 4: Place an outbound callStep 5: Read call historyIf it failsRelated