Sending media
Media (images, audio, video, documents and stickers) is sent
through the same POST /v1/messages endpoint used for text. The media
fields go inside the content object. The type comes
from the anchor field present (image_url, video_url,
audio_url, document_url or sticker_url).
Supported media types
| Variant | Anchor field | Common formats | Maximum size | caption? | filename? |
|---|---|---|---|---|---|
image | image_url | JPEG, PNG, WebP | 16 MB | ✓ | — |
video | video_url | MP4 | 100 MB | ✓ | — |
audio | audio_url | MP3, OGG, M4A | 16 MB | — | — |
document | document_url | PDF, DOCX, XLSX, etc. | 100 MB | ✓ | ✓ |
sticker | sticker_url | WebP (static or animated), exactly 512×512px | 500 KB | — | — |
caption is not accepted for audio and sticker. The API returns
422 if the field is sent with those types.
After sending, only the caption of an image, video or document can be edited. The file, audio and stickers cannot: deleting is the only change. See what can be changed after sending.
Method: public URL
Provide a publicly accessible HTTPS URL. Turbo Notify downloads the file before delivering it to WhatsApp.
Image
curl -X POST \ https://api.turbonotify.com/v1/messages \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": {"number": "+5511988887777"}, "content": { "image_url": "https://cdn.example.com/product-photo.jpg", "mime_type": "image/jpeg", "caption": "Product photo" } }'await fetch('https://api.turbonotify.com/v1/messages', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ to: { number: '+5511988887777' }, content: { image_url: 'https://cdn.example.com/product-photo.jpg', mime_type: 'image/jpeg', caption: 'Product photo' } })});import requests
requests.post( "https://api.turbonotify.com/v1/messages", headers={ "Authorization": "Bearer YOUR_KEY", "Content-Type": "application/json", }, json={ "to": {"number": "+5511988887777"}, "content": { "image_url": "https://cdn.example.com/product-photo.jpg", "mime_type": "image/jpeg", "caption": "Product photo", }, },)Video
curl -X POST \ https://api.turbonotify.com/v1/messages \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": {"number": "+5511988887777"}, "content": { "video_url": "https://cdn.example.com/product-clip.mp4", "mime_type": "video/mp4", "caption": "Watch the video" } }'await fetch('https://api.turbonotify.com/v1/messages', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ to: { number: '+5511988887777' }, content: { video_url: 'https://cdn.example.com/product-clip.mp4', mime_type: 'video/mp4', caption: 'Watch the video' } })});import requests
requests.post( "https://api.turbonotify.com/v1/messages", headers={ "Authorization": "Bearer YOUR_KEY", "Content-Type": "application/json", }, json={ "to": {"number": "+5511988887777"}, "content": { "video_url": "https://cdn.example.com/product-clip.mp4", "mime_type": "video/mp4", "caption": "Watch the video", }, },)A video caption also accepts @<number> mentions.
Audio
curl -X POST \ https://api.turbonotify.com/v1/messages \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": {"number": "+5511988887777"}, "content": { "audio_url": "https://cdn.example.com/audio.ogg", "mime_type": "audio/ogg" } }'await fetch('https://api.turbonotify.com/v1/messages', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ to: { number: '+5511988887777' }, content: { audio_url: 'https://cdn.example.com/audio.ogg', mime_type: 'audio/ogg' } })});import requests
requests.post( "https://api.turbonotify.com/v1/messages", headers={ "Authorization": "Bearer YOUR_KEY", "Content-Type": "application/json", }, json={ "to": {"number": "+5511988887777"}, "content": { "audio_url": "https://cdn.example.com/audio.ogg", "mime_type": "audio/ogg", }, },)mime_type is optional and does not change how the file is sent: the format
WhatsApp receives comes from what the server behind the URL actually delivers.
Document
curl -X POST \ https://api.turbonotify.com/v1/messages \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": {"number": "+5511988887777"}, "content": { "document_url": "https://cdn.example.com/invoice.pdf", "mime_type": "application/pdf", "filename": "Invoice-April.pdf", "caption": "Your invoice for April" } }'await fetch('https://api.turbonotify.com/v1/messages', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ to: { number: '+5511988887777' }, content: { document_url: 'https://cdn.example.com/invoice.pdf', mime_type: 'application/pdf', filename: 'Invoice-April.pdf', caption: 'Your invoice for April' } })});import requests
requests.post( "https://api.turbonotify.com/v1/messages", headers={ "Authorization": "Bearer YOUR_KEY", "Content-Type": "application/json", }, json={ "to": {"number": "+5511988887777"}, "content": { "document_url": "https://cdn.example.com/invoice.pdf", "mime_type": "application/pdf", "filename": "Invoice-April.pdf", "caption": "Your invoice for April", }, },)filename appears as the attachment name in the recipient’s chat. A document
caption also accepts @<number> mentions.
Sticker
curl -X POST \ https://api.turbonotify.com/v1/messages \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": {"number": "+5511988887777"}, "content": { "sticker_url": "https://cdn.example.com/animated-sticker.webp", "mime_type": "image/webp" } }'await fetch('https://api.turbonotify.com/v1/messages', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ to: { number: '+5511988887777' }, content: { sticker_url: 'https://cdn.example.com/animated-sticker.webp', mime_type: 'image/webp' } })});import requests
requests.post( "https://api.turbonotify.com/v1/messages", headers={ "Authorization": "Bearer YOUR_KEY", "Content-Type": "application/json", }, json={ "to": {"number": "+5511988887777"}, "content": { "sticker_url": "https://cdn.example.com/animated-sticker.webp", "mime_type": "image/webp", }, },)Stickers must be WebP (static or animated), with exact dimensions of 512×512 pixels (any other size is rejected even if the WebP is valid), and stay within the 500 KB limit.
Field schema (all types)
The media fields go inside the content object. The anchor field (e.g. image_url) determines the variant. Every media send uses a public URL; you cannot send a file by giving only its key in your BYO Storage.
Field in content | Type | Required | Notes |
|---|---|---|---|
image_url | URL | conditional | Anchor field for images; public HTTPS URL |
video_url | URL | conditional | Anchor field for videos; public HTTPS URL |
audio_url | URL | conditional | Anchor field for audio; public HTTPS URL |
document_url | URL | conditional | Anchor field for documents; public HTTPS URL |
sticker_url | URL | conditional | Anchor field for stickers; public HTTPS URL |
mime_type | string | no | optional and informational: it does not change the send. The format is identified from the file downloaded from the URL |
caption | string | partial | accepted for image, video, document; not accepted for audio and sticker (returns 422). Accepts @<number> mention tokens, detected implicitly, with the same rules as text |
filename | string | no | document only; appears as the attachment name in the chat |
Response
{
"message_id": "msg_a1b2c3d4e5f60718293a4b5c6d7e8f90",
"status": {
"state": "pending",
"at": "2026-04-27T10:00:05Z",
"error": null
}
} The actual confirmation arrives through the message.sent webhook. If the send
fails (unreachable URL, unsupported format, size exceeded) you receive
message.send.failed with a specific error object ({ code, message, hint }).
Outbound webhook
When WhatsApp confirms the send, the message.sent webhook
includes content_type and preview (same format as message.received):
{ "id": "evt_3f9a1c2b4d5e6f708192a3b4c5d6e7f8", "type": "message.sent", "organization_id": "org_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", "number_alias": "main", "number_id": "num_0a1b2c3d4e5f60718293a4b5c6d7e8f9", "created_at": "2026-04-27T10:00:08Z", "data": { "id": "msg_a1b2c3d4e5f60718293a4b5c6d7e8f90", "from": { "alias": "main", "number": "5511973117056" }, "to": { "number": "5511988887777" }, "preview": "Product photo", "content_type": "image", "direction": "outbound", "channel": "whatsapp", "origin": "tn", "at": "2026-04-27T10:00:08Z" }}To download the file of a received media message, use
GET /v1/numbers/{ref}/messages/{id}: the media fields
appear directly in the object named after the content_type (image, video,
audio, document, sticker), with no media sub-object. With BYO Object Storage
set up, that object comes with kind: "available" and a url holding the
presigned download URL.
Related
- Sending messages: text, location, contact, calendar (same endpoint).
- Outbound webhook:
message.sentandmessage.deliveredpayloads. - Get a message: fetch the full body, including media.