Overview
Media messages let you send non-text content through the same Cloud API message endpoint:
POST https://api.dualhook.com/v25.0/<PHONE_NUMBER_ID>/messages
Supported outbound types: image, video, audio, document, sticker.
In Dualhook architecture, your backend sends through the connection-scoped runtime. Inbound media webhooks are delivered directly to your endpoint via Webhook Override. Outbound and requested media stream through Dualhook without persistence or caching.
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.