Skip to main content
Images, videos, documents, voice notes and stickers all work the same way: you give us a public link and we fetch the file from it.
There is no upload endpoint. You cannot post a file to this API. If your file is not already on the internet somewhere, put it there first.
That is a deliberate scope decision, not an oversight. Hosting uploads would mean storage, quotas, retention rules and virus scanning, none of which exist here yet.

Try it right now, with a file you do not have to host

Before you set up hosting for your own files, prove the whole path works using a file that is already public. These two are ordinary test files published by the W3C — the standards body behind HTML — so they are about as stable and as neutral as a link on the internet gets. Copy either one straight into the url field.
Send the image with /v1/messages/image and the PDF with /v1/messages/document:
The test panel in the portal has both of these behind a Use a sample link, so you can prove that invoices arrive as invoices without hosting anything at all.
These are for testing. Do not build an integration that points at somebody else’s server — host your own files, as below.
If you are not a developer, this is the part that sounds harder than it is. You need somewhere on the internet that will hand out your file when asked. Any of these does that:

Somewhere you already have

  • Your own website — upload the file and use its address.
  • Your online store or CMS media library.
  • Whatever your accounting or invoicing software already uses to email PDFs. If it can email a link, that link usually works here.

Somewhere built for it

  • An object storage bucket — the big cloud providers all offer one, and they are the normal answer for anything ongoing.
  • An image or media hosting service.
  • A free file-sharing site, for a quick test.
We do not recommend one provider over another, and nothing here needs an account with anyone in particular. What matters is only the shape of the link.
The one test that settles it. Copy your link into a private / incognito browser window and open it. If the file itself downloads or displays straight away — no sign-in, no preview page, no “click here to download” — it will work. If anything else happens, it will not.

Things that look like they should work and do not

A Google Drive, Dropbox or OneDrive “share” link will not work, even set to anyone with the link. Those addresses open a web page that shows your file in a viewer. We need the file itself, and a viewer page is not a file — the send comes back as 422 media_fetch_failed.If your file is in one of those, the reliable fix is to put a copy somewhere that serves it directly, rather than hunting for a direct-download trick — those tricks change without notice and break quietly.
Two more worth knowing:
  • Free file-sharing sites often expire, or delete after the first download. Fine for one test; not for an integration that has to keep working next month.
  • A link that needs a password or a sign-in will never work. Our servers open it as an anonymous visitor, exactly like your private browser window.
A link works when opening it in a private browser window downloads the file itself with no login and no intermediate page.

Works

A direct link to the bytes. Signed URLs are fine as long as they are still valid at the moment you call the API.

Fails

Preview pages, share pages and video sites serve HTML, not a file. Private addresses are refused outright.

The rules, exactly

Every refusal gives the same message — Field "url" must be a publicly reachable http(s) URL to the file. — regardless of which rule was broken. That is on purpose: it is actionable either way, and a per-reason message would let someone map a private network from outside by watching which hosts produce which wording.

The two failures, and how to tell them apart

The URL failed a check before anything was fetched: wrong scheme, malformed, too long, credentials in it, or a private address.Fix the URL. Retrying the same one will never work. details.field names the field — url, or buttons[N].url for a link button.
The URL looked fine but the file could not be downloaded: a 404, an expired signed link, a hostname that does not resolve, a TLS failure, or a page where a file was expected.Open the link in a private browser window. If it does not download the raw file for you, it will not for us either.

Which endpoint for which file

Always set filename on a document. It is the name the recipient sees and saves. Without it the name comes from the URL, which is often something like download.php or a signed-URL blob.

Formats

We do not restrict file types — WhatsApp decides what it can render. In practice:
  • Images — JPEG and PNG are safest.
  • Video — MP4 with H.264 video and AAC audio.
  • Audio — MP3 and OGG/Opus arrive as playable voice notes.
  • Stickers — WebP. Other formats may arrive as an ordinary image or not render at all.
  • Documents — anything. PDF is the most predictable across devices.
mime_type is an optional hint you can send with image, video and document. It is checked for the shape type/subtype only — we never verify it matches the actual file, so a wrong hint is worse than none.

Sizes

There is no size limit in this API, but there are two real ones underneath:
  • WhatsApp’s own attachment limits. A file that is too big for WhatsApp will fail however it was sent.
  • Time. The file has to be fetched during your request. A large file over a slow link can exhaust the request budget and come back as 502 upstream_unavailable.
Keep media small. A 200 KB receipt sends instantly; a 40 MB video is a gamble. The file is fetched during your API call, not later. So:
  • A signed URL must still be valid at the moment you send. Generate it right before the call, not the night before.
  • Do not delete the file immediately after the API returns 200. The fetch has already happened by then, but keeping it around a while makes debugging far easier.
  • If your storage is private by default, generate a short-lived public link for the send.