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

# Make and Receive WhatsApp Voice Calls

> Place business-initiated calls, answer user calls, request call permissions, and use the built-in dashboard softphone with WhatsApp Business Calling.

WhatsApp Business Calling lets you place and receive **voice calls** with your customers over WhatsApp. Pilot Status handles the signaling; how the audio flows depends on which kind of number you use.

<Note>
  Calling works on **two provider types**:

  * **Meta Cloud API numbers** — official WhatsApp Business Platform numbers. Media flows over **WebRTC** (browser ↔ WhatsApp), so you exchange **SDP** offers/answers.
  * **Pilot Status web numbers** (unofficial / QR-connected) — media is handled **server-side**. **No SDP** is exchanged; you drive audio through the `/play` and `/realtime-session` endpoints instead.

  Any other number type returns `400 FEATURE_NOT_SUPPORTED`. A web number that isn't connected returns `409 WHATSAPP_INSTANCE_NOT_CONNECTED`.
</Note>

## The two call types

| Type                              | Who starts it          | Billing                                                                                                                                                             |
| --------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **BIC** — Business-Initiated Call | You call the customer  | On **Meta** numbers: billed by **Meta directly on your WABA** (per minute, 6-second pulses, only when answered). On **web** numbers: **no Meta per-minute charge**. |
| **UIC** — User-Initiated Call     | The customer calls you | **Free** on both.                                                                                                                                                   |

Pilot Status **does not charge** anything for calls. On Meta numbers, minutes are measured in **6-second pulses** (fractions round up to a whole pulse) and priced by destination country and monthly volume tier — see [Meta's official calling pricing](https://developers.facebook.com/documentation/business-messaging/whatsapp/calling/pricing) and the [billing details](/api/calls/overview#meta-pricing-for-business-initiated-calls-official-numbers).

## What both providers share

Regardless of provider, these endpoints work the same way:

* `POST /v1/calls` — place a business-initiated call
* `GET /v1/calls` — list calls
* `GET /v1/calls/{callId}` — get one call
* `POST /v1/calls/{callId}/accept` — answer a ringing call
* `POST /v1/calls/{callId}/reject` — decline
* `POST /v1/calls/{callId}/terminate` — hang up

And the same webhooks fire for both: `call.ringing`, `call.connected`, `call.ended` (includes `duration` in seconds when answered), `call.missed`, and `call.permission_updated`.

The difference is **how media is negotiated** — SDP (Meta) vs. server-side (web) — and a few provider-specific endpoints described below.

## Easiest path: the dashboard softphone

The dashboard **/chat** page has a built-in **softphone** that works on **both number types** — answer, place, reject, and hang up WhatsApp calls with no code at all. On Meta numbers it uses browser WebRTC; on Pilot Status web numbers the audio is handled server-side. This is the fastest way to try calling. Use the API below when you want to build your own calling experience.

<Frame caption="The built-in softphone in /chat — place, answer, and hang up WhatsApp calls; call events (incoming, missed, declined) appear in the conversation thread.">
  <img src="https://mintcdn.com/iaxp/xoabh69SG54diA0y/images/dashboard/softphone-call.png?fit=max&auto=format&n=xoabh69SG54diA0y&q=85&s=597c019c6e660276b050a3f97de0ffe9" alt="Dashboard softphone in /chat showing an active WhatsApp call" width="1917" height="840" data-path="images/dashboard/softphone-call.png" />
</Frame>

***

## Meta Cloud API numbers

### Requirements

1. A connected **Meta Cloud API** number.
2. Calling **enabled** on the number: `PUT /v1/calls/settings` with `{ "status": "ENABLED" }`. Numbers on a messaging tier below **2,000/day cannot enable calling** (Meta error `138015`).
3. For business-initiated calls, a valid **call permission** from the user (see below).
4. A payment method attached to your WABA — Meta bills business-initiated call minutes; without one the call fails with Meta error 131044 (`META_CALLING_PAYMENT_REQUIRED`).

<Note>
  `GET`/`PUT /v1/calls/settings`, `GET /v1/calls/permissions`, `POST /v1/calls/permissions/request`, and `POST /v1/calls/{callId}/pre-accept` are **Meta-only**. On a web number they return `400 FEATURE_NOT_SUPPORTED`.
</Note>

### Step 1 — Get permission to call (BIC only)

You can only start a business-initiated call after the user grants a **call permission**. Request it inside the 24-hour customer service window:

```bash theme={null}
curl -X POST https://pilotstatus.com.br/v1/calls/permissions/request \
  -H "Content-Type: application/json" \
  -H "x-api-key: ps_your_token_here" \
  -d '{ "to": "+5511999999999", "text": "Can we call you about your order?" }'
```

The user's reply arrives as a `call.permission_updated` webhook. Check the current permission any time:

```bash theme={null}
curl "https://pilotstatus.com.br/v1/calls/permissions?to=+5511999999999" \
  -H "x-api-key: ps_your_token_here"
```

Returns `no_permission`, `temporary`, or `permanent` plus per-action limits. Without permission, `POST /v1/calls` returns `META_CALL_PERMISSION_REQUIRED`.

### Step 2 — Place a call (BIC)

Your WebRTC client produces an SDP offer; pass it to Pilot Status, which relays it to WhatsApp. Audio then flows browser ↔ WhatsApp directly.

```bash theme={null}
curl -X POST https://pilotstatus.com.br/v1/calls \
  -H "Content-Type: application/json" \
  -H "x-api-key: ps_your_token_here" \
  -d '{ "to": "+5511999999999", "sdp": "<RFC 8866 SDP offer>", "sdpType": "offer" }'
```

```json theme={null}
HTTP 201
{ "id": "call_abc", "externalCallId": "wacid.HBg...", "status": "RINGING" }
```

### Step 3 — Receive a call (UIC)

When a customer calls you, a `call.ringing` webhook fires. Answer with your SDP answer:

```bash theme={null}
curl -X POST https://pilotstatus.com.br/v1/calls/call_abc/accept \
  -H "Content-Type: application/json" \
  -H "x-api-key: ps_your_token_here" \
  -d '{ "sdp": "<RFC 8866 SDP answer>" }'
```

* `POST /v1/calls/{callId}/pre-accept` — optional early answer that reduces audio clipping; the call still connects only on `accept`.
* `POST /v1/calls/{callId}/reject` — decline (no body).
* `POST /v1/calls/{callId}/terminate` — hang up an active call (no body).

***

## Pilot Status web numbers (unofficial)

Web numbers connect by QR code and handle media **server-side** — there's no SDP, no WebRTC client, and **no permission or settings surface**. You place and answer calls, then drive the audio with two web-only endpoints.

<Warning>
  On web numbers, **do not send an `sdp`** to `POST /v1/calls` or `/accept` — the fields `sdpOffer` / `sdpAnswer` stay `null`. Calling `pre-accept`, the `settings` endpoints, or the `permissions` endpoints returns `400 FEATURE_NOT_SUPPORTED`.
</Warning>

### Place or answer a call

No permission request and no settings toggle are required. Just place the call:

```bash theme={null}
curl -X POST https://pilotstatus.com.br/v1/calls \
  -H "Content-Type: application/json" \
  -H "x-api-key: ps_your_token_here" \
  -d '{ "to": "+5511999999999" }'
```

Answer an incoming call with no body:

```bash theme={null}
curl -X POST https://pilotstatus.com.br/v1/calls/call_abc/accept \
  -H "x-api-key: ps_your_token_here"
```

Then reject or terminate with the same endpoints as Meta (no body).

### Drive the audio (web-only)

Once a call is connected, use these to control the audio stream:

<CodeGroup>
  ```bash Push an audio file theme={null}
  # Backend downloads your audio file and plays it into the call
  curl -X POST https://pilotstatus.com.br/v1/calls/call_abc/play \
    -H "Content-Type: application/json" \
    -H "x-api-key: ps_your_token_here" \
    -d '{ "url": "https://example.com/greeting.ogg" }'
  ```

  ```bash Open a realtime PCM16 session theme={null}
  # Returns a single-use wsUrl (dedicated public media host, ~2 min TTL)
  curl -X POST https://pilotstatus.com.br/v1/calls/call_abc/realtime-session \
    -H "x-api-key: ps_your_token_here"
  ```
</CodeGroup>

* **`/play`** — the backend fetches your audio file (through an SSRF guard) and plays it into the live call. Great for greetings, IVR prompts, or pre-recorded messages.
* **`/realtime-session`** — opens a **full-duplex PCM16 WebSocket** for real-time audio (e.g. an AI voice agent). The returned `wsUrl` lives on a dedicated public media host, uses a **single-use token** with a **\~2-minute TTL**, and you connect to it **directly** — it is not proxied and the HTTP layer does not upgrade the WebSocket.

***

## Follow call progress with webhooks

Subscribe to these events on your webhook — they fire for **both** Meta and web numbers: `call.ringing`, `call.connected`, `call.ended` (includes `duration` in seconds when answered), `call.missed`, and `call.permission_updated`. On Meta numbers, the native `calls` envelope is also delivered for custom WebRTC signaling.

## Next steps

<CardGroup cols={2}>
  <Card title="Calls API Reference" icon="phone" href="/api/calls/endpoints">
    Every /v1/calls endpoint, parameters, and settings.
  </Card>

  <Card title="Calling Overview" icon="circle-info" href="/api/calls/overview">
    Signaling model, billing details, and limits.
  </Card>
</CardGroup>
