Media messages

Images, audio, video, documents and stickers all send the same way: upload the file once to get a mediaId, then send that mediaId. Splitting the upload from the send lets you reuse one uploaded file across many recipients and keeps the send body tiny.


Step 1 — upload the file

Post the raw file as multipart form-data (field name file) to the channel's media route. Telenow uploads it to WhatsApp and hands back a mediaId — a Telenow id (UUID), not a Meta media id — plus the file's details.

curl -X POST https://api.telenow.ai/api/app-whatsapp/channels/b1e2c3d4-…/media \
  -H "Authorization: Bearer vai_app_…" \
  -F "[email protected]"
{
  "success": true,
  "data": {
    "mediaId": "5f0c2b1e-8d4a-4b6e-9c3f-2a7d1e0b9c84",
    "mime": "application/pdf",
    "filename": "invoice-1234.pdf",
    "bytes": 84213
  }
}

The message type is inferred from the MIME type you upload with — the Content-Type of the multipart part (curl sets it from the file name; set it yourself in code). You never pass a type: application/pdf becomes a document, image/jpeg an image, image/webp always a sticker. A part sent without a type is treated as application/octet-stream and goes as a document. If the file exceeds its cap you get a 413 (WhatsApp caps image/jpeg files at 5 MB) before anything is sent (see caps).

  • A mediaId belongs to the channel it was uploaded on — sending it on another channel fails (upload not found for this channel).
  • WhatsApp keeps the uploaded file for about 30 days; upload again after that.
  • Uploads work on Meta Cloud numbers (native or your own Meta app). Other numbers return 400.
  • Don't send an Idempotency-Key header on uploads larger than 5 MB — with that header the request body is capped at 5 MB (413 body too large).

Step 2 — send the mediaId

Post to /send with the mediaId from step 1. Add a caption for image, video or document; omit it for audio and stickers.

curl -X POST https://api.telenow.ai/api/app-whatsapp/send \
  -H "Authorization: Bearer vai_app_…" \
  -H "Content-Type: application/json" \
  -d '{
    "channelId": "b1e2c3d4-…",
    "to": "+919876543210",
    "mediaId": "5f0c2b1e-8d4a-4b6e-9c3f-2a7d1e0b9c84",
    "caption": "Your invoice for order #1234"
  }'
{ "success": true, "data": { "wamid": "wamid.HBgLMTQxNTU1MDEwMBUCABIYFj…" } }

Media is free-form content, so it only sends inside the 24-hour customer service window; outside it, start with a template. As with every send, track delivery via whatsapp.message.status, and use an Idempotency-Key header (or dedupe on the wamid) to make retries safe — see Text messages. A mediaId that isn't a UUID is refused with 422 before anything is sent.

Media types

Each type has its own size cap and its own rule about captions. A caption is a text string that rides along with the file; WhatsApp only supports it on visual/attachment types.

Image

JPEG or PNG, up to 5 MB. Caption allowed. The caption renders under the image and is the natural place for a short line about what the customer is looking at.

Video

MP4, up to 16 MB. Caption allowed. Same placement as an image caption.

Document

Any file (PDF, spreadsheet, etc.), up to 100 MB. Caption allowed — it shows as the message text above the file card. The original filename from your upload is what the customer sees and downloads.

Audio

Up to 16 MB. No caption — WhatsApp renders audio as a standalone playable clip with no accompanying text, so a send that includes a caption is refused; send the text as a separate message.

Sticker

WebP, up to 500 KB. No caption. Stickers are square, transparent-background images and, like audio, carry no text. Any image/webp upload is sent as a sticker.

Size caps & captions

TypeMax sizeCaption?
Image5 MBYes
Video16 MBYes
Document100 MBYes
Audio16 MBNo
Sticker500 KBNo

Exceeding a cap fails the upload with 413 — the file never reaches the send step. A caption on audio or a sticker is rejected (502, WhatsApp doesn't allow a caption on audio messages — send the text separately), not dropped.

Quote a message

Add reply_to (the wamid of the message you're answering) to the send body to thread your media under it — the same field documented in Reply to a specific message. It works alongside mediaId, location, interactive, and every other content type:

{ "channelId": "b1e2c3d4-…", "to": "+919876543210",
  "mediaId": "5f0c2b1e-8d4a-4b6e-9c3f-2a7d1e0b9c84",
  "caption": "Here's your receipt.",
  "reply_to": "wamid.HBgLMTQx…" }

Receiving media

When a customer sends you a photo, voice note, or file, it arrives on the whatsapp.message.received webhook. Telenow fetches the bytes first and then sends the event once: content.media describes the attachment (id, mime, filename), and the top-level media object carries a signedUrl you can download from for about 1 hour (it can be null if the link couldn't be made). If the fetch failed, media holds a fetchError instead of the link.

{
  "event": "whatsapp.message.received",
  "channelId": "b1e2c3d4-…",
  "messageId": "e5b2c9a4-1f7d-4c8a-9b2e-0a1b2c3d4e5f",
  "from": "+919876543210",
  "wamid": "wamid.HBgLM…",
  "type": "image",
  "content": {
    "media": { "id": "1122334455", "mime": "image/jpeg", "filename": "photo.jpg" }
  },
  "media": {
    "mime": "image/jpeg",
    "filename": "photo.jpg",
    "signedUrl": "https://…"
  },
  "timestamp": "2026-07-18T09:31:00+00:00"
}

To download later (after the signed link expires), use GET /media/{id}. The {id} is the Telenow message id (a UUID) — the messageId in the webhook above, or the id from GET /api/app-whatsapp/threads/{id}/messages (ready-made in media.url). It is not the wamid, and not content.media.id.

curl -L https://api.telenow.ai/api/app-whatsapp/media/e5b2c9a4-1f7d-4c8a-9b2e-0a1b2c3d4e5f \
  -H "Authorization: Bearer vai_app_…" \
  -o inbound-photo.jpg

The response streams the raw bytes as an attachment (Content-Disposition: attachment with the file name, X-Content-Type-Options: nosniff, Cache-Control: private, no-store); SVG, XML and HTML files are always served as application/octet-stream. See Webhooks & events for the full received payload and signature verification.

With your org API key

With your org API key (X-API-Key: vai_live_…) the same three steps use the org routes; the request and response bodies are the same, except that the channel goes in the path:

StepOrg-key route
UploadPOST /api/orgs/{orgId}/whatsapp-hub/channels/{channelId}/media — multipart field file, same { mediaId, mime, filename, bytes } back
SendPOST /api/orgs/{orgId}/whatsapp-hub/channels/{channelId}/send — { "to", "mediaId", "caption"?, "reply_to"? } (or …/threads/{threadId}/send without to)
DownloadGET /api/orgs/{orgId}/whatsapp-hub/media/{messageId} — images, audio and video are served inline, other files as attachments
curl -X POST https://api.telenow.ai/api/orgs/{orgId}/whatsapp-hub/channels/{channelId}/media \
  -H "X-API-Key: vai_live_…" \
  -F "[email protected]"

Media needs a Meta Cloud number (native or your own Meta app) on both surfaces; on other numbers the upload returns 400 and a media send returns 502. Uploads and sends need an owner, admin or developer key.