wapi.
Guides

Media

Upload, send by URL, and decrypt what arrives — inbound media is encrypted.

Media is sent by URL. imageUrl, videoUrl, audioUrl, documentUrl and stickerUrl are fetched server-side when the message goes out, so whatever you point at has to still resolve at send time — not just when you make the call.

If you don't already host the file, upload it first and use the URL you get back. That one is permanent.

Uploading

curl -X POST https://api.wapi.crafter.run/api/upload \
  -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: image/png' \
  --data-binary @photo.png
{ "success": true, "publicUrl": "https://api.wapi.crafter.run/media/<uuid>/photo.png" }

Then send it:

{ "to": "+51999888777",
  "imageUrl": "https://api.wapi.crafter.run/media/<uuid>/photo.png",
  "text": "optional caption" }

Uploads cap at 16 MB.

Inbound media is encrypted

This is the part that surprises people. WhatsApp hands out a CDN link plus a mediaKey, and the bytes behind that link are useless without decryption — download them directly and you get ciphertext, not an image.

Take the imageMessage node (or videoMessage, audioMessage, documentMessage, stickerMessage) from the webhook payload and post it straight through:

curl -X POST https://api.wapi.crafter.run/api/decrypt-media \
  -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"data":{"messages":{"message":{"imageMessage":{ ...from webhook... }}}}}'
{ "success": true,
  "publicUrl": "https://api.wapi.crafter.run/media/<uuid>.jpg?expires=1750000000&sig=<hmac>" }

You do not need to unwrap the webhook payload — pass the node as it arrived.

Two kinds of URL

The difference matters when you decide what to store.

UploadDecrypt
Shape/media/<uuid>/<name>/media/<uuid>.jpg?expires=…&sig=…
LifetimePermanentOne hour
Safe to store?YesNo — store the bytes, not the link

A decrypted URL is signed and expires. If you need to keep inbound media, fetch it and put it in your own storage within the hour; persisting the link gives you a dead reference tomorrow.

What goes wrong

StatusMeaning
422Neither a body nor a recognisable media node.
413Over 16 MB.
503Storage is not configured on this deployment. GET /health reports it.

In a sandbox

decrypt-media returns a fixed PNG rather than doing real decryption — there is no real ciphertext to decrypt. That is enough to exercise your handler's plumbing, but it means a sandbox cannot tell you whether your image processing works on real WhatsApp media.

On this page