Skip to content

Send media

Two ways to attach a file, and which one you want depends on how often you send the same thing.

Give us a public URL. We fetch it and upload it to WhatsApp.

{
"to": "+966500000000",
"type": "document",
"document": {
"link": "https://example.com/invoices/1001.pdf",
"filename": "Invoice-1001.pdf",
"caption": "Your invoice for order 1001"
}
}

Simplest for anything generated per customer, like an invoice: you already have a URL and it is used once.

The URL must be publicly reachable over http or https. Private and internal addresses are refused before the fetch, and refused again at connection time if DNS resolves to one. Maximum 15 MB.

If you already uploaded the file to Meta, pass its id and skip the transfer.

{
"to": "+966500000000",
"type": "image",
"image": { "id": "1234567890123456", "caption": "This week's offers" }
}

Worth doing for anything sent to many people, such as a campaign image: upload once, reference it thousands of times, and no bytes move per send.

typeFieldNotes
imageimageJPEG or PNG. caption supported.
videovideoMP4 or 3GPP. caption supported.
documentdocumentAny type. filename is shown to the recipient.
audioaudioAAC, MP4, MPEG, AMR or OGG. No caption.

WhatsApp’s own size limits apply and differ per type. A file it refuses comes back as upstream_failure with Meta’s explanation.

Audio can be sent as a push-to-talk note rather than an attached file:

{
"to": "+966500000000",
"type": "audio",
"audio": { "link": "https://example.com/reply.ogg", "voice": true }
}

It appears in the chat as a recorded message with a waveform. WhatsApp expects OGG with the Opus codec for this; other formats send as a plain audio file.

Without one we fall back to the last segment of the URL, and then to document, so a link ending in a generated identifier produces exactly the filename you would not want.

Incoming media messages report has_media: true. Fetch the bytes with the message id:

Terminal window
curl https://k-message.kerneltics.com/v1/media/6f1c2d3e-... \
-H "Authorization: Bearer km_live_YOUR_KEY" \
-o received.jpg

The response is the file with its original content type, not JSON. It needs the messages:read scope.