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 theurl field.
/v1/messages/image and the PDF with /v1/messages/document:
These are for testing. Do not build an integration that points at somebody else’s server — host your own files, as below.
Getting a link for your own file
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.
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
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.
What a good link looks like
A link works when opening it in a private browser window downloads the file itself with no login and no intermediate page.Works
Fails
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
400 invalid_media_url — we refused to even try
400 invalid_media_url — we refused to even try
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.422 media_fetch_failed — we tried and could not get the file
422 media_fetch_failed — we tried and could not get the file
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
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.
Keeping links alive
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.

