Media in Wapito is one field and one store. The field is media on every send endpoint that
carries a file, and it accepts three forms: a public URL, a data URI, or the id of a file you
uploaded earlier. The store is where those uploads live, and where every inbound file is
copied to the moment it arrives, so a photo a customer sent this morning is still there long after
WhatsApp itself has discarded it. This page covers both directions, the size and type limits, and
the signed links that let a browser fetch a file without ever seeing your channel token.
Sending a file
Seven endpoints send media, one per bubble type WhatsApp renders:
| Endpoint | What the recipient sees |
|---|---|
POST /messages/image | A photo with an optional caption. JPEG, PNG or WebP. |
POST /messages/video | A video with an optional caption. |
POST /messages/document | A file bubble with the name you give it. Bytes are never re-encoded. |
POST /messages/sticker | A sticker. WebP only; no caption. |
POST /messages/audio | An audio file with a play bar. |
POST /messages/voice | A voice note: the waveform bubble, with a played receipt. Wapito converts MP3, WAV and M4A to the Opus-in-OGG format WhatsApp needs. |
POST /messages/short | A round video note. |
They share one request body: to, media, and depending on the type caption and filename.
curl -X POST https://api.wapito.com/v1/messages/document \
-H "Authorization: Bearer $WAPITO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "+15551234567",
"media": "https://acme.example/invoices/4182.pdf",
"filename": "Invoice-4182.pdf",
"caption": "Invoice for order #4182"
}'
The three forms of media
A URL. https:// only, and the host must resolve to a public address. Wapito probes it
with a HEAD request — that is where the size and the type are read from — and the protocol
engine then downloads it, so the URL is never shown to the recipient and never has to be
reachable from WhatsApp itself. Up to two redirects are followed, each one checked again. A URL
that points at a private range, a plain http URL, or one that does not answer within ten
seconds is refused with 400 invalid_request and a details.reason saying why.
A data URI. data:application/pdf;base64,JVBERi0… — the bytes inline. Convenient for small
files generated on the fly; for anything large, upload first.
A media id. med_01JRQ8F4X9N2K7YB3C5V6W8H0T, returned by an upload or found on any media
message. The file is already in the store, so a send is immediate and the same id can go to a
hundred recipients without a hundred fetches. This is the right form for anything you send more
than once.
When the type or the name cannot be inferred — an upload endpoint that serves every file as
application/octet-stream, say — pass an object instead of a string:
{ "to": "+15551234567", "media": { "url": "https://acme.example/dl?id=4182", "mimetype": "application/pdf", "filename": "Invoice-4182.pdf" } }
Uploading first
POST /media takes a multipart form with a
file part and an optional filename, and returns the stored file:
curl -X POST https://api.wapito.com/v1/media \
-H "Authorization: Bearer $WAPITO_TOKEN" \
-F "file=@./Invoice-4182.pdf" \
-F "filename=Invoice-4182.pdf"
{
"id": "med_01JRQ8F4X9N2K7YB3C5V6W8H0T",
"channel_id": "ch_01JRQ8F4X9N2K7YB3C5V6W8H0T",
"filename": "Invoice-4182.pdf",
"mimetype": "application/pdf",
"size_bytes": 48210,
"link": "https://api.wapito.com/v1/media/med_01JRQ8F4X9N2K7YB3C5V6W8H0T?exp=1790064121&sig=8f2c1d94b6a70e35",
"created_at": "2026-09-15T07:22:01.000Z",
"expires_at": "2026-09-22T07:22:01.000Z"
}
Uploads expire like inbound files do (below), so an upload is a staging area for the sends that
follow, not an archive. DELETE /media/{id}
removes one early.
Limits
Two checks run before any WhatsApp traffic happens, so a file that is too big or the wrong type costs the number nothing:
- Size. 16 MB on Sandbox and 64 MB on Premium — the decoded bytes of a data URI, the
multipart body of an upload, or the
Content-Lengtha URL reports. Over the cap answers413 payload_too_large. The cap is also inGET /channel/limitsasmedia_max_bytes. - Type. The store keeps an allow-list, not a deny-list, of media types: the common image
formats (JPEG, PNG, GIF, BMP, TIFF, HEIC), WebP for stickers, MP4, 3GPP, QuickTime, WebM and
Matroska video, MP3, AAC, OGG, Opus, WAV, AMR and WebM audio, and PDF, Office, OpenDocument,
RTF and ZIP documents. Anything else — SVG above all, which can carry script — answers
415 unsupported_media_type. The type comes from the URL'sContent-Typeheader, the data URI's declared type or themimetypeyou pass explicitly — never from the file extension. A server that answers without a usableContent-Typegets400 invalid_requestwithdetails.reason: "media_mimetype_required"until you pass one.
Media sends count toward the daily sent quota exactly like text, one per message.
Receiving a file
An inbound photo, document or voice note arrives as a messages event whose message object has
type set and one typed field beside it:
{
"id": "false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55",
"chat_id": "15551234567@s.whatsapp.net",
"from": "15551234567",
"from_me": false,
"type": "image",
"timestamp": 1789459200000,
"image": {
"id": "med_01JRQ8F4X9N2K7YB3C5V6W8H0T",
"link": "https://api.wapito.com/v1/media/med_01JRQ8F4X9N2K7YB3C5V6W8H0T?exp=1790064121&sig=8f2c1d94b6a70e35",
"mime_type": "image/jpeg",
"file_size": 184213,
"file_name": null,
"caption": "Here is the receipt"
}
}
By the time the event reaches you the file has been copied out of the protocol engine into the
Wapito store, and link points at that copy. The engine itself keeps a file for about an hour;
the copy is what makes the link usable for days. If the copy failed — a file WhatsApp had already
dropped, or one that could not be decrypted — the event still ships, with
{ "id": null, "error": "unavailable" } in place of the media block, so a missing file never
delays the message that carried it.
Your webhook handler should return 200 first and fetch the file from a worker: media downloads
are the slowest thing a handler can do inline, and the ten-second delivery timeout
is for the acknowledgement, not for the download.
Signed links and expiry
A link is a URL to GET /media/{id} with two
query parameters, exp and sig. The signature covers the media id and the expiry, so neither
can be edited to reach another file or extend the window, and the link needs no Bearer
header — that is what makes it safe to hand to a browser, an e-mail, or a downstream service
that must never hold the channel token. The same endpoint also accepts the channel token, without
exp and sig, for your own back end.
| Plan | Inbound and uploaded files are kept for |
|---|---|
| Sandbox | 24 hours |
| Premium | 7 days |
That window is the file's expires_at, and it is also how long its signed links work. After it
the file is deleted and the link answers 404 not_found. Two consequences:
- If you need the file for longer, fetch it and store your own copy. The store is a hand-off, not an archive, and the security page lists this retention alongside everything else Wapito keeps.
- A link you saved yesterday may have expired even though the file has not, if you are close to
the window.
GET /messages/{id}/mediareturns the file's current record with a freshly signed link.
# Any HTTP client can follow a signed link — no Authorization header.
curl -o receipt.jpg \
"https://api.wapito.com/v1/media/med_01JRQ8F4X9N2K7YB3C5V6W8H0T?exp=1790064121&sig=8f2c1d94b6a70e35"