> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kapso.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# WhatsApp Calling

> Enable voice calls and connect your own media server or voice agent through Kapso

## What you need

* A dedicated WhatsApp Cloud API number connected to your Kapso project.
* A Kapso API key from that project and the number's **Meta phone number ID**.
* A public HTTPS endpoint for call webhooks.
* A media server that handles WebRTC audio and creates real SDP offers and answers.

Kapso forwards call events and proxies call actions. Your server or voice provider runs the agent and handles audio. An API key and webhook alone do not create a voice agent.

<Note>
  Kapso's messaging sandbox is not a Calling test number. Coexistence numbers continue to use calls in the WhatsApp Business App; calls through Kapso require a dedicated Cloud API number. Meta decides Calling eligibility and country availability. Check [Meta's current prerequisites](https://developers.facebook.com/documentation/business-messaging/whatsapp/calling) before choosing a number.
</Note>

## 1. Configure your application

Keep these values in your application's `.env`, excluded from Git:

```dotenv theme={null}
KAPSO_API_KEY=your_project_api_key
WHATSAPP_PHONE_NUMBER_ID=your_meta_phone_number_id
META_GRAPH_VERSION=v24.0
WHATSAPP_WEBHOOK_SECRET=your_random_webhook_secret
PUBLIC_WEBHOOK_URL=https://your-app.example/webhooks/whatsapp
```

The phone number ID is different from the display phone number, WABA ID, project ID, and Kapso configuration UUID. You can find it in **Phone numbers** in the dashboard, or list your numbers:

```bash theme={null}
curl --fail-with-body \
  'https://api.kapso.ai/platform/v1/whatsapp/phone_numbers' \
  -H "X-API-Key: $KAPSO_API_KEY"
```

Load these variables with your application's dotenv support. For a trusted shell-compatible `.env`, run `set -a`, `source .env`, then `set +a` before using the shell examples below. Provider keys for speech recognition, reasoning, or speech synthesis belong on your server too.

## 2. Enable Calling

```bash theme={null}
curl --fail-with-body -X PATCH \
  "https://api.kapso.ai/platform/v1/whatsapp/phone_numbers/$WHATSAPP_PHONE_NUMBER_ID" \
  -H "X-API-Key: $KAPSO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"whatsapp_phone_number":{"calls_enabled":true}}'
```

Then read Meta's settings through the proxy:

```bash theme={null}
curl --fail-with-body \
  "https://api.kapso.ai/meta/whatsapp/$META_GRAPH_VERSION/$WHATSAPP_PHONE_NUMBER_ID/settings?fields=calling" \
  -H "X-API-Key: $KAPSO_API_KEY"
```

Confirm `calling.status` is `ENABLED`. A Meta eligibility rejection must be resolved before testing an agent; changing the voice model or webhook will not enable an ineligible number.

## 3. Register a Meta webhook

Calling events retain Meta's payload structure, scoped to the connected number. Use a phone-number webhook with `kind: "meta"`, rather than a Kapso message-event subscription or project webhook.

First check for an existing receiver:

```bash theme={null}
curl --fail-with-body \
  "https://api.kapso.ai/platform/v1/whatsapp/phone_numbers/$WHATSAPP_PHONE_NUMBER_ID/webhooks?kind=meta" \
  -H "X-API-Key: $KAPSO_API_KEY"
```

If your application already processes messages from a `kind: "kapso"` webhook, adding a Meta receiver also exposes Meta `messages` changes. Choose one path for normal message processing to avoid duplicate replies; keep Calling permission replies on the Meta receiver.

Only one Meta webhook is allowed per number, including inactive ones. If one exists, reuse its receiver or deliberately update it; replacing it changes where other Meta events go too. In the dashboard, check whether a voice agent is already assigned to the number. Choose which component answers its calls before testing; do not let the assigned agent and your receiver both answer.

If there is no Meta webhook, create one. Use `jq` to safely encode your URL and secret:

```bash theme={null}
jq -n \
  --arg url "$PUBLIC_WEBHOOK_URL" \
  --arg secret "$WHATSAPP_WEBHOOK_SECRET" \
  '{whatsapp_webhook:{kind:"meta",url:$url,secret_key:$secret,active:true}}' \
  | curl --fail-with-body -X POST \
    "https://api.kapso.ai/platform/v1/whatsapp/phone_numbers/$WHATSAPP_PHONE_NUMBER_ID/webhooks" \
    -H "X-API-Key: $KAPSO_API_KEY" \
    -H 'Content-Type: application/json' \
    --data-binary @-
```

`secret_key` is required and is separate from your Kapso API key. Save the returned webhook ID. Repeat the webhook-list request above and confirm the URL and `active: true` before calling.

## 4. Connect audio

```mermaid theme={null}
flowchart LR
  WhatsApp[WhatsApp caller] <--> Meta[Meta]
  Meta -->|Call event| Kapso[Kapso]
  Kapso -->|Signed HTTPS webhook| Agent[Your media server and agent]
  Agent -->|Call action and SDP| Kapso
  Kapso -->|Call action and SDP| Meta
  Meta <-->|WebRTC audio| Agent
```

HTTPS carries events and SDP. Audio needs its own reachable WebRTC path. For a local test, a public HTTPS tunnel such as Tailscale Funnel can deliver webhooks, but it does not by itself make UDP media reachable. Configure a TURN relay if your server's network requires one. See [receiving calls](/docs/whatsapp/calling/receive-calls) and [connecting a voice agent](/docs/whatsapp/calling/voice-agents).

ICE selects a network path for the media connection. A STUN server helps discover a reachable address; a TURN server relays audio when a direct connection cannot be established. These are separate from your webhook URL.

Once your receiver and agent are running, call the connected number from WhatsApp. You can also [send a voice call button](/docs/whatsapp/send-messages/voice-call) that the user taps to call your business.

## Other Meta Calling capabilities

Meta also documents [SIP, voicemail, call recording, transcription, and partner call routing](https://developers.facebook.com/documentation/business-messaging/whatsapp/calling). These require separate configuration and validation; the WebRTC guides here do not establish that those features work in your deployment. Messaging handoff does not transfer a live call.

## Troubleshooting

| Symptom | Check |
| - | - |
| Number not found or access denied | API key belongs to the project containing this Meta phone number ID. |
| Calling cannot be enabled | Dedicated Cloud API connection and Meta's eligibility response. Save the error code and subcode. |
| Calls never reach your server | Active `kind: meta` receiver, its URL, and webhook delivery logs. |
| Signature verification fails | Raw request bytes, `X-Webhook-Signature`, and the receiver's `secret_key`. |
| Call connects but has no audio | ICE connection, firewall/TURN configuration, incoming audio frames, and outgoing audio frames. |
| Greeting plays, then the agent stops responding | Committed STT text, turn detection, the text submitted to the model, and provider errors. A greeting-only test is insufficient. |
| API returns non-JSON | Inspect HTTP status and a bounded response body before parsing; confirm the API hostname and path. |
| Outbound call is rejected | Current `start_call.can_perform_action`, number/country support, and any Kapso billing error. |

<CardGroup cols={2}>
  <Card title="Receive calls" icon="phone" href="/docs/whatsapp/calling/receive-calls">
    Verify events, answer a call, and end the session.
  </Card>

  <Card title="Connect a voice agent" icon="microphone" href="/docs/whatsapp/calling/voice-agents">
    Connect Pipecat, Pipecat Cloud, or ElevenLabs Agents to the Calling API.
  </Card>

  <Card title="Make outbound calls" icon="phone" href="/docs/whatsapp/calling/outbound-calls">
    Check permission, dial, and apply the caller's SDP answer.
  </Card>

  <Card title="TypeScript SDK" icon="code" href="/docs/whatsapp/typescript-sdk/calls">
    Use the call action, permission, and log helpers.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.