What Recording and Transcription Are
The Calling API can record and transcribe the audio of business-initiated and user-initiated calls made through Cloud API. Both are opt-in per call: you decide when you place or accept each call.
Recording and transcription are independent features. Each has its own request object (recording, transcription) and its own webhook event (call_recording_available, call_transcription_available). Enabling recording does not produce a transcript, and enabling transcription does not produce an audio file. You can enable either, both, or neither.
When a call is opted in, both participants hear a short, legally required announcement before recording or transcription starts. After the call ends you receive a webhook with a media ID for the finished audio file or JSON transcript.
Dualhook Boundary
Dualhook documents call recording and transcription but does not offer them. There is no calling screen in the dashboard, and the Runtime API has no /calls route, so you can't place or accept a call, or attach a recording or transcription object, through Dualhook. Use your own Meta app with access to the number, and download the files with that app's token.
The Calling API, and with it recording and transcription, is not available on Coexistence numbers, which most Dualhook connections are. Voice and video calls on those numbers stay in the WhatsApp Business app.
The call_recording_available and call_transcription_available events arrive on the calls webhook field. Dualhook's Meta app does not subscribe to calls, so it does not ingest, store, or forward these webhooks, and a Dualhook connection does not deliver them, even with Webhook Override active. Subscribe your own Meta app to calls to receive them. Dualhook never holds recordings or transcripts.
Prerequisites
- Calling API enabled on the business phone number (see Calling Configuration).
- Your app subscribed to the
callswebhook field. - For business-initiated calls, an open conversation or a call permission from the WhatsApp user.
Enable on a Business-Initiated Call
Add a recording object, a transcription object, or both to the connect request:
POST /<PHONE_NUMBER_ID>/calls
{
"messaging_product": "whatsapp",
"to": "14085551234",
"recipient": "US.13491208655302741918",
"action": "connect",
"session": {
"sdp_type": "offer",
"sdp": "<<RFC 8866 SDP>>"
},
"recording": {
"status": "ENABLED",
"purpose": "quality assurance",
"announcement_language": "en_US"
}
}
recipient lets you identify the user by BSUID instead of, or in addition to, the phone number in to.
Enable on a User-Initiated Call
Add the same object to the accept request:
{
"messaging_product": "whatsapp",
"call_id": "wacid.ABGGFjFVU2AfAgo6V",
"action": "accept",
"session": {
"sdp_type": "answer",
"sdp": "<<RFC 8866 SDP>>"
},
"transcription": {
"status": "ENABLED",
"purpose": "quality assurance",
"announcement_language": "en_US"
}
}
To accept a call without recording or transcribing it, omit the object or send it with "status": "DISABLED".
Request Object Reference
recording and transcription take the same fields:
| Field | Required | Notes |
|---|---|---|
status | Yes | ENABLED to opt the call in, DISABLED to opt out explicitly. |
purpose | When status is ENABLED | Spoken to both participants as part of the announcement. Max 250 characters. Write it in the language of announcement_language. |
announcement_language | When status is ENABLED | Locale code for the spoken announcement, e.g. en_US or es. See Announcement Languages. |
Announcements and Consent
Before any audio is recorded or transcribed, the Calling API mixes a spoken announcement into both the business and the user audio streams, built from your purpose and announcement_language:
- Recording: "The audio of this call will be recorded for the following purpose: <purpose>."
- Transcription: "The audio of this call will be transcribed for the following purpose: <purpose>."
Recording or transcription starts only after the announcement finishes. A participant who doesn't consent can end the call before or during the announcement. A request with status: ENABLED and no purpose is rejected.
Announcement Languages
These announcement_language values have a localized announcement:
| Language | announcement_language | Recording | Transcription |
|---|---|---|---|
| English | en (also en_US, en_AU, en_CA, en_GB, en_IN, en_NZ) | Yes | Yes |
| Dutch | nl | Yes | Accepted, plays the English announcement |
| French | fr | Yes | Yes |
| German | de | Yes | Yes |
| Hindi | hi | Yes | Yes |
| Italian | it | Yes | Yes |
| Kannada | kn | Yes | Yes |
| Portuguese (Brazil) | pt | Yes | Yes |
| Spanish (Latin America) | es | Yes | Yes |
| Spanish (Spain) | es_ES | Yes | Accepted, plays the English announcement |
| Telugu | te | Yes | Yes |
| Vietnamese | vi | Yes | Yes |
announcement_language only sets the announcement. It does not set the transcript language, which Meta detects from the audio (see Transcript Language).
Recording and Transcription on the Same Call
Send both objects to get both an audio recording and a transcript. Participants then hear one combined announcement: "The audio of this call will be recorded and transcribed for the following purpose: <purpose>." It is localized in the same languages as the recording announcement.
When both objects are present, the purpose and announcement_language from recording are used and the values in transcription are ignored. You still receive one webhook per feature.
Recording-Available Webhook
After the call ends and post-processing finishes (typically under a minute), Meta sends call_recording_available on the calls field:
{
"object": "whatsapp_business_account",
"entry": [{
"id": "<WABA_ID>",
"changes": [{
"field": "calls",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"phone_number_id": "<BUSINESS_PHONE_NUMBER_ID>",
"display_phone_number": "<BUSINESS_DISPLAY_PHONE_NUMBER>"
},
"calls": [{
"id": "wacid.HBgLMTQxMjYxMzYyNTMVAgASGCBGO...",
"from": "<USER_PHONE_NUMBER>",
"from_user_id": "<BSUID>",
"from_parent_user_id": "<PARENT_BSUID>",
"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=133..."
}
}
}]
}
}]
}]
}
| Field | Notes |
|---|---|
call_recording.type | Always audio for now. |
audio.id | Media ID. Use the Media API to get a fresh download URL. |
audio.sha256 | Base64 SHA-256 of the file, to verify the download. |
audio.mime_type | e.g. audio/ogg; codecs=opus. |
audio.url | Short-lived download URL (see Download and Retention). |
from_user_id and from_parent_user_id identify the user by BSUID. from may be omitted when the user has adopted a username.
Transcription-Available Webhook
call_transcription_available has the same envelope, with a call_transcript object instead of call_recording:
{
"id": "wacid.HBgLMTQxMjYxMzYyNTMVAgASGCBGO...",
"from": "<USER_PHONE_NUMBER>",
"from_user_id": "<BSUID>",
"from_parent_user_id": "<PARENT_BSUID>",
"timestamp": "1728932177",
"event": "call_transcription_available",
"call_transcript": {
"document": {
"id": "1002764438271669",
"sha256": "Y9vvGyeo3n76ptkXu3CwDBsnzbRFqpjHskQdMGSVqas=",
"mime_type": "application/json",
"url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=133..."
}
}
}
document.id, sha256, and url work like the recording fields. document.mime_type is always application/json.
Transcript Language
You don't set a transcription language. Meta detects the spoken language from the audio and reports it as an ISO 639 code (e.g. en) in transcript.language, independent of announcement_language.
Supported languages change as Meta's speech models improve. Currently: Afrikaans, Albanian, Arabic, Azerbaijani, Bengali, Bulgarian, Burmese, Cebuano, Chinese, Croatian, Czech, Danish, Dutch, English, Finnish, French, German, Greek, Guarani, Gujarati, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Javanese, Kannada, Korean, Macedonian, Malay, Malayalam, Marathi, Norwegian, Persian, Polish, Portuguese, Punjabi, Romanian, Russian, Serbian, Sinhala, Slovak, Slovenian, Spanish, Swahili, Swedish, Tagalog (Filipino), Tamil, Telugu, Thai, Turkish, Urdu, and Vietnamese.
For a call in an unsupported language you still get the webhook, but the transcript may be empty.
Transcript Document Format
The downloaded transcript is a JSON document with two top-level objects:
{
"metadata": {
"processed_at": "2026-06-18T20:16:47Z",
"audio": { "duration": 21.76, "sample_rate": 16000, "channels": 2, "audio_format": "stereo" }
},
"transcript": {
"text": "[Business] Hello, how about you? [Customer] Hey, I'm good. How are you?",
"language": "en",
"duration": 21.76,
"confidence": 0.83,
"segments": [
{
"id": 1,
"speaker": "Business",
"channel": 0,
"start": 1.16,
"end": 2.44,
"text": "Hello, how about you?",
"confidence": 0.85,
"words": [
{ "word": "Hello,", "start": 1.16, "end": 1.64, "confidence": 0.89, "lang": "en" }
]
}
]
}
}
| Field | Notes |
|---|---|
metadata.processed_at | ISO 8601 UTC time post-processing finished. |
metadata.audio | duration (seconds), sample_rate (Hz), channels (2 for a two-party call), audio_format (e.g. stereo). |
transcript.text | Whole conversation as one string, each segment prefixed with [Business] or [Customer]. |
transcript.language | Detected language, ISO 639. |
transcript.duration | Transcribed audio length in seconds. |
transcript.confidence | Overall score, 0 to 1. |
segments[].speaker / channel | Business on channel 0, Customer (the WhatsApp user) on channel 1. Attribution holds when people talk over each other. |
segments[].start / end | Seconds from the start of the call audio. |
segments[].words[] | word, start, end, confidence, and lang (ISO 639) per word. |
Download and Retention
Recordings and transcripts use the same download flow as media messages:
- The
urlin the webhook is valid for 5 minutes. Download it with an authenticatedGETusing your access token. - After that, fetch a fresh URL from the Media API with
audio.idordocument.id.
Files stay available for 7 days after the webhook is delivered. Then the media ID expires and Meta deletes the file. Copy anything you need to keep to your own storage within that window.
Errors
Request errors specific to these features (the same for recording and transcription):
| Scenario | Cause |
|---|---|
| Missing purpose | status is ENABLED but purpose is omitted or empty. |
| Purpose too long | purpose exceeds 250 characters. |
Invalid announcement_language | Not a supported locale code. |
Invalid status | Not ENABLED or DISABLED. |
See Calling Troubleshooting for general calling errors.
Pricing and Known Issues
Recording and transcription are currently free. Meta plans to price each one separately later and says it will publish rates with advance notice first; there is no launch date. See Calling Pricing.
Known issue (as of Meta's Aug 31, 2026 update): when the WhatsApp user changes networks during a call, the recording and transcript can be cut short. Meta is rolling out a fix.