Overview
Media messages let you send non-text content through the same Cloud API message endpoint:
POST https://graph.facebook.com/<GRAPH_VERSION>/<PHONE_NUMBER_ID>/messages
Supported outbound types: image, video, audio, document, sticker.
In Dualhook architecture, sending is performed by your backend directly to Meta Graph API. Inbound media webhooks are delivered directly to your endpoint via Webhook Override. Dualhook does not proxy or store media binaries.
Supported Formats and Size Limits
| Type | Typical supported formats | Max size |
|---|---|---|
| Image | jpeg, png | 5 MB |
| Audio | aac, amr, mp3, m4a, ogg (Opus) | 16 MB |
| Video | mp4, 3gp | 16 MB |
| Document | pdf, doc, docx, ppt, pptx, xls, xlsx, txt | 100 MB |
| Sticker | webp | Static 100 KB, animated 500 KB |
document,image, andvideocan include captions.audioandstickerdo not use captions.- Validate MIME type and file extension consistency before upload/send.
Send Media by Media ID
Recommended flow: upload media first to get a media_id, then send using id.
{
"messaging_product": "whatsapp",
"to": "<RECIPIENT_PHONE_NUMBER>",
"type": "image",
"image": {
"id": "<MEDIA_ID>",
"caption": "<OPTIONAL_CAPTION>"
}
}
For document sends, include filename:
{
"messaging_product": "whatsapp",
"to": "<RECIPIENT_PHONE_NUMBER>",
"type": "document",
"document": {
"id": "<MEDIA_ID>",
"filename": "<DOCUMENT_FILENAME>",
"caption": "<OPTIONAL_CAPTION>"
}
}
For upload instructions, see Upload, Retrieve & Delete Media.
Send Media by Public URL
{
"messaging_product": "whatsapp",
"to": "<RECIPIENT_PHONE_NUMBER>",
"type": "image",
"image": {
"link": "https://<PUBLIC_MEDIA_HOST>/<MEDIA_PATH>",
"caption": "<OPTIONAL_CAPTION>"
}
}
URL requirements:
- URL must be publicly reachable over HTTPS.
- The endpoint must return the binary file directly.
Content-Typeshould match the real file type.
Voice Messages vs Basic Audio
Audio messages have two presentations:
| Voice message | Basic audio | |
|---|---|---|
| Codec / format | .ogg encoded with OPUS codec, mono | Any supported audio format (mp3, aac, m4a, amr, etc.) |
| Auto-download | Yes (≤ 512 KB) | No (user taps to download) |
| Profile picture | Shows business profile image | Generic music icon |
| Icon | Microphone | Music note (or microphone if file is OPUS-encoded .ogg) |
| Auto-transcription | Available if user enabled "Automatic" voice transcripts | Available if file is OPUS-encoded .ogg and user enabled transcripts |
| Recipient sees a play icon | Yes (≤ 512 KB) | Only if recipient has audio auto-download enabled and conditions are met (e.g. on Wi-Fi) |
To send a voice message, set voice: true and provide an OPUS-encoded .ogg file:
{
"messaging_product": "whatsapp",
"to": "12015550123",
"type": "audio",
"audio": {
"id": "<MEDIA_ID>",
"voice": true
}
}
If you set voice: true with a non-OPUS file, voice-message transcription will silently fail. To send a regular audio file, omit voice or set it to false.
Voice Played Status Webhook
Starting March 17, 2026, voice messages emit a new status webhook the first time a recipient plays a voice message you sent. The status is played and arrives via the standard messaging-status webhook envelope.
{
"statuses": [
{
"id": "wamid.HBgL...",
"status": "played",
"timestamp": "1742227200",
"recipient_id": "12015550123",
"conversation": { "id": "<CONVERSATION_ID>" },
"pricing": { /* same shape as other status webhooks */ }
}
]
}
Only the first play triggers the webhook — subsequent replays do not. The webhook only fires for voice messages (audio with voice: true); basic audio messages do not get a played status.
In Dualhook setups, status webhooks are delivered directly to your endpoint via Webhook Override — Dualhook does not ingest them. Add played to the set of status values your handler recognizes if you want to track voice-message engagement.
Inbound Media Webhooks
Inbound media from users includes a media object with an ID. Your backend should use that media ID to retrieve the binary from Meta.
{
"messages": [
{
"from": "<WHATSAPP_USER_PHONE_NUMBER>",
"id": "<WHATSAPP_MESSAGE_ID>",
"timestamp": "<TIMESTAMP>",
"type": "image",
"image": {
"id": "<MEDIA_ID>",
"mime_type": "image/jpeg",
"sha256": "<MEDIA_SHA256>",
"caption": "<OPTIONAL_CAPTION>"
}
}
]
}
Error Handling
- Message API acceptance does not guarantee recipient delivery.
- Final outcomes arrive via status webhooks.
- Prefer
media_idsends for higher reliability. - Keep retry logic with exponential backoff for
429and5xx. - Avoid tight retry loops for repeated
4xxvalidation failures.