> ## 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.

# Recordings and transcripts

> Turn on Meta's native call recording and transcription, then play, read, and download them through Kapso

Meta can record and transcribe WhatsApp calls on request. Kapso saves the metadata from Meta's completion events and fetches the audio or transcript from Meta when you ask for it. Kapso does not turn capture on for you, keep its own copy, or run extra speech-to-text.

Complete [Calling setup](/docs/whatsapp/calling/overview) and get a normal call working first.

<Note>
  Kapso's recording and transcript support is rolling out. If a call detail response has no `artifacts` field, it isn't available for your project yet.
</Note>

## Turn on capture for a call

Recording and transcription are separate, per-call opt-ins. Add either object, or both, to the request that starts the call:

* **Inbound:** the `accept` request. Do not add them to `pre_accept`.
* **Outbound:** the `connect` request, next to the usual `to`/`recipient` and SDP offer.

```json theme={null}
{
  "messaging_product": "whatsapp",
  "call_id": "wacid.ABGGFjFVU2AfAgo6V",
  "action": "accept",
  "session": { "sdp_type": "answer", "sdp": "ANSWER_SDP_FROM_YOUR_MEDIA_SERVER" },
  "recording": {
    "status": "ENABLED",
    "purpose": "control de calidad",
    "announcement_language": "es"
  },
  "transcription": {
    "status": "ENABLED",
    "purpose": "control de calidad",
    "announcement_language": "es"
  }
}
```

Send it to the same `POST https://api.kapso.ai/meta/whatsapp/{version}/{phone_number_id}/calls` endpoint you already use. Kapso passes both objects to Meta unchanged.

| Field | Notes |
| - | - |
| `status` | `ENABLED` or `DISABLED`. Omitting the object also means no capture. |
| `purpose` | Required when enabled. Up to 250 characters, written in the announcement language. Meta reads it aloud. |
| `announcement_language` | Required when enabled, for example `en_US`, `es`, `es_ES`, `pt`, `fr`, `de`, or `hi`. See Meta's [supported languages](https://developers.facebook.com/documentation/business-messaging/whatsapp/calling/call-recording/#supported-announcement-languages). |

Before capture starts, Meta plays an announcement to both participants, for example: *"El audio de esta llamada se grabará con el siguiente propósito: control de calidad."* Either participant can hang up instead. With both objects enabled, Meta plays one combined announcement and uses the `recording` object's `purpose` and `announcement_language`.

* Recording alone gives you audio and no transcript. Transcription alone gives you a transcript and no audio.
* `announcement_language` only controls the announcement. Meta detects the spoken language for the transcript; an unsupported spoken language can produce an empty transcript.
* Meta's prerequisites still apply: Calling enabled on the number and your app subscribed to the `calls` webhook field. Meta currently documents a known issue where a caller changing networks can cut a recording or transcript short.

Keep your normal call flow: start the agent after `accept` succeeds or, for outbound calls, after `ACCEPTED` and media are ready. The announcement plays at the start of the call; plan your greeting around it.

## Completion events

After the call ends and Meta finishes processing, Meta sends one event per enabled feature on the `calls` field. Kapso forwards them through your signed `kind: "meta"` webhook like any other Calling event.

| `event` | Media object |
| - | - |
| `call_recording_available` | `call_recording.audio` |
| `call_transcription_available` | `call_transcript.document` |

```json theme={null}
{
  "field": "calls",
  "value": {
    "metadata": { "phone_number_id": "PHONE_NUMBER_ID" },
    "calls": [{
      "id": "wacid.HBgLMTQxMjYxMzYyNTMVAgASGCBGO",
      "from_user_id": "US.13491208655302741918",
      "timestamp": "1728932177",
      "event": "call_recording_available",
      "call_recording": {
        "type": "audio",
        "audio": {
          "id": "1002764438271669",
          "sha256": "Y9vvGyeo3n76ptkXu3CwDBsnzbRFqpjHskQdMGSVqas=",
          "mime_type": "audio/ogg; codecs=opus",
          "url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=..."
        }
      }
    }]
  }
}
```

These events arrive after `terminate`, can share a delivery with other events, and can carry only a business-scoped user ID instead of a phone number. In your [receiver](/docs/whatsapp/calling/receive-calls#verify-the-webhook):

* Verify the raw-body signature and iterate over every entry, change, and call, as for other Calling events.
* Route them as metadata, not as call lifecycle. A completion event must not start a media session, restart an agent, or reopen a finished call.
* Accept them even after you have cleaned up the call's session.

You don't need to download from the webhook `url`. It expires after five minutes and needs Meta credentials. Use Kapso's endpoints below instead.

**Logs → Meta Logs** shows the completion webhooks, including media IDs and checksums. It does not show the audio or the transcript text.

## In the dashboard

Open **WhatsApp → More → Calls** and click a call. The **Call Details** panel shows:

* **Recording:** click **Load recording** to play it in the browser, or **Download recording**.
* **Transcript:** readable text with speaker labels and timestamps, plus **Download transcript (JSON)**.

Each available artifact shows its estimated expiry. "No recording received" or "No transcript received" means Kapso hasn't received a completion event for that call; it does not mean capture was off or still processing. Use **Refresh** to check again.

These sections only cover Meta's native recordings and transcripts. Recordings from your own voice runtime don't appear here.

## Fetch through the API

Use your project API key on the developer API host:

```text theme={null}
GET https://app.kapso.ai/api/v1/whatsapp_calls/{id}
GET https://app.kapso.ai/api/v1/whatsapp_calls/{id}/artifacts/recording
GET https://app.kapso.ai/api/v1/whatsapp_calls/{id}/artifacts/transcription
```

`{id}` is Kapso's call UUID, not Meta's `wacid...` call ID. Look up the UUID from the Meta call ID with [List calls](/api/meta/whatsapp/calls/list-calls):

```bash theme={null}
curl --fail-with-body --get \
  "https://api.kapso.ai/meta/whatsapp/$META_GRAPH_VERSION/$WHATSAPP_PHONE_NUMBER_ID/calls" \
  -H "X-API-Key: $KAPSO_API_KEY" \
  --data-urlencode "call_id=$META_CALL_ID" \
  --data-urlencode "limit=1"
```

Use `data[0].id`. An empty `data` array means Kapso has no saved call with that ID for this number. The call list does not include artifacts.

The TypeScript SDK has no artifact helper. Use `fetch`:

```javascript theme={null}
const KAPSO_APP_API = 'https://app.kapso.ai/api/v1';
const headers = { 'X-API-Key': process.env.KAPSO_API_KEY };

async function getCallArtifacts(callUuid) {
  const response = await fetch(`${KAPSO_APP_API}/whatsapp_calls/${callUuid}`, { headers });
  if (!response.ok) throw new Error(`Call detail failed: HTTP ${response.status}`);
  const { data } = await response.json();
  return data.artifacts;
}
```

### Call detail

`data.artifacts` has a `recording` and a `transcription` entry:

```json theme={null}
{
  "data": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "call_id": "wacid.HBgLMTQxMjYxMzYyNTMVAgASGCBGO",
    "artifacts": {
      "recording": {
        "state": "available",
        "media_id": "1002764438271669",
        "mime_type": "audio/ogg; codecs=opus",
        "sha256": "Y9vvGyeo3n76ptkXu3CwDBsnzbRFqpjHskQdMGSVqas=",
        "received_at": "2026-10-06T12:00:00Z",
        "expires_at": "2026-10-13T11:59:30Z",
        "fetch_path": "/api/v1/whatsapp_calls/123e4567-e89b-12d3-a456-426614174000/artifacts/recording"
      },
      "transcription": { "state": "absent" }
    }
  }
}
```

| `state` | Meaning |
| - | - |
| `absent` | No completion event received. Not proof that capture was off. |
| `available` | Metadata received and inside the estimated retention window. Fetching can still fail. |
| `expired` | The estimated window has passed. Metadata stays; `fetch_path` is omitted. |

Meta keeps recordings and transcripts for seven days. Kapso estimates `expires_at` from the earlier of the event timestamp and the first time Kapso received it, plus seven days. Repeated events don't extend it. Meta can remove the media earlier.

`fetch_path` is relative to `https://app.kapso.ai`. Each fetch asks Meta for a fresh media URL with the number's credentials, so don't store or reuse webhook URLs.

### Recording

```bash theme={null}
curl --fail-with-body \
  "https://app.kapso.ai/api/v1/whatsapp_calls/$CALL_UUID/artifacts/recording?download=true" \
  -H "X-API-Key: $KAPSO_API_KEY" \
  -o call-recording.ogg
```

Returns the whole audio file with Meta's MIME type, up to 32 MiB. Without `download=true` it is served inline. Range requests are not supported; load the file once and seek locally.

### Transcript

Without `download=true`, you get a readable preview:

```json theme={null}
{
  "data": {
    "state": "available",
    "text": "[Business] Hola, gracias por llamar. [Customer] Quiero confirmar mi cita.",
    "language": "es",
    "duration": 42.5,
    "segments": [
      { "id": 1, "speaker": "Business", "channel": 0, "start": 0.0, "end": 2.1, "text": "Hola, gracias por llamar.", "confidence": 0.94 },
      { "id": 2, "speaker": "Customer", "channel": 1, "start": 2.4, "end": 4.8, "text": "Quiero confirmar mi cita.", "confidence": 0.91 }
    ],
    "truncated": false
  }
}
```

* `duration`, `start`, and `end` are seconds. Channel `0` is your business and `1` is the customer; `speaker` is Meta's label.
* Optional segment fields are omitted when Meta doesn't send them. `language` and `duration` are `null` when missing.
* The preview returns up to 500 segments and 100,000 characters, without word-level detail. `truncated: true` means it was shortened.
* `state: "empty"` means Meta returned a valid, empty transcript. Kapso does not retranscribe.
* Render `text` as plain text.

`?download=true` returns Meta's original transcript JSON, including metadata and word-level detail, up to 2 MiB.

### Errors

```json theme={null}
{
  "error": { "code": "artifact_download_failed", "message": "Call artifact could not be retrieved" },
  "artifact": { "kind": "recording", "state": "download_failed" }
}
```

| Status | `error.code` | What to do |
| - | - | - |
| 404 | `artifact_unavailable` | No metadata, unknown kind, or Meta no longer has the media. |
| 410 | `artifact_expired` | The retention window has passed. |
| 413 | `artifact_too_large` | Over 32 MiB (audio) or 2 MiB (transcript). |
| 422 | `artifact_invalid_transcript` | Meta's transcript couldn't be parsed. Try `?download=true`. |
| 502 | `artifact_download_failed` | Meta's download failed. Retry. |

A call outside your project returns `404` with `{"error": "Call not found"}`. A missing or invalid API key returns `401` with `{"error": "Invalid or missing API key"}`. Artifact responses are `Cache-Control: private, no-store`.

## Keep a copy

Kapso does not archive recordings or transcripts. To keep them past Meta's retention, download them while they are `available` and store them yourself.

If your voice runtime records or transcribes calls itself, for example with Pipecat's audio buffer or ElevenLabs Agents' post-call data, that runs separately from Meta's native capture. Those files stay with your runtime and don't appear in Kapso's call details.

## Pricing

Meta currently doesn't charge for native recording or transcription, on top of normal call rates. Meta plans separate pricing for both; it hasn't published rates or a date. See [Calling pricing](/docs/whatsapp/pricing-faq#how-is-whatsapp-calling-billed).


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