Images, video, audio, documents and stickers - sent by URL, received as a message id you download.
There is no multipart upload. You give the bridge a public HTTP(S) URL and it fetches the file itself, which means the file has to be reachable from the internet - a signed S3 link, a public CDN path, an endpoint of your own.
The kind of message is decided by the Content-Type the URL responds with, not by the file extension or by a field you set. A URL that serves image/jpeg becomes an image; one that serves application/pdf becomes a document. A server that answers application/octet-stream for everything is the usual reason media arrives as a nondescript file.
curl -X POST https://wabridges.com/api/instances/user-123/proxy/send/media \
-H "Authorization: Bearer $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chat": "15550001234",
"url": "https://example.com/photo.jpg",
"caption": "check this out"
}'
{
"message_id": "ACE41E988162AB19A4A7BB7A9E663693",
"timestamp": 1777180605
}
Only chat and url are required. caption is optional and ignored for kinds that cannot carry one.
Stickers are the one special case: set as_sticker and serve the file as image/webp. Anything else is rejected, and a caption on a sticker is dropped.
{"chat": "15550001234", "url": "https://example.com/photo.jpg", "caption": "check this out"}
{"chat": "15550001234", "url": "https://example.com/report.pdf", "caption": "Q1 report"}
{"chat": "15550001234", "url": "https://example.com/voice.ogg"}
{"chat": "15550001234", "url": "https://example.com/sticker.webp", "as_sticker": true}
An inbound photo arrives on your webhook as an ordinary message event with type: "media" and media_type saying which kind. The bytes are not in the payload - webhooks stay small and fast. What you get is the message_id, and that is the handle you download with.
A caption, when there is one, arrives in caption rather than body. An image sent with no caption has both empty, which is worth handling before you index on message text.
{
"event": "message",
"type": "media",
"media_type": "image",
"body": "",
"caption": "look at this",
"chat_id": "15550001234@s.whatsapp.net",
"contact_id": "15550001234@s.whatsapp.net",
"from_me": false,
"is_group": false,
"message_id": "ACE7253B69494A9CB9D671C943A7F4AB",
"name": "John Doe",
"phone": "15550001234",
"timestamp": 1777180605
}
Fetch the bytes with the message_id from the event. The response is the raw file with its real content type - image/jpeg, audio/ogg, video/mp4, or application/octet-stream when it is anything else.
The bridge caches media for 3 hours. Inside that window this is cheap and repeatable; after it, the fetch can return 404. If you need to keep a file, copy it to your own storage when the webhook arrives rather than downloading it on demand later.
curl https://wabridges.com/api/instances/user-123/proxy/messages/ACE7253B69494A9CB9D671C943A7F4AB/media \ -H "Authorization: Bearer $WA_API_KEY" \ --output inbound.jpg
Download in the background, not inside your webhook handler - the handler still has to answer 2xx quickly or deliveries queue up behind it.
Something here not matching what you see? Write to us - a person answers.