API Cloud (MEDIA v2)
These routes are the corrected shape: the channel first and the media identifier last, as in the rest of the channel API. The previous ones still work without changes and are documented on the Media page.
Besides the order, this shape fixes two things:
- Download: the response always reports the content type, the real size of the file and a filename, and it does not forward the headers that only apply between Meta and the proxy. The previous route forwards Meta’s headers as they come, including the size Meta declares.
- Upload: the file type sent to Meta is the one declared by the
filepart when Meta accepts it, and is inferred from the content when it is not. The previous route always infers it from the content, which mislabels several of the formats Meta accepts.
All routes share the base https://channels.chattigo.com/bsp-cloud-chattigo-isv and the Authorization: <JWT> authentication header (the raw JWT, with no Bearer or Basic prefix), obtained from POST /login.
{version} is client-chosen and forwarded to Meta as-is: Meta manages the Graph API version lifecycle, so it must never be pinned.
How it connects to the rest of the API
Downloading the media of an inbound message
- Request the token with
POST /loginand send it in theAuthorizationheader on every call. - Your webhook receives the inbound message payload. Two pieces of data the route needs come from it: the channel, in
metadata.display_phone_number, and the media identifier, inmessages[].audio.id. - Download the file with
GET /{version}/{did}/media/{media_id}and save it with the extension that matches the responseContent-Type.
curl --request POST \
--url 'https://channels.chattigo.com/bsp-cloud-chattigo-isv/login' \
--header 'Content-Type: application/json' \
--data '{"username": "<user>", "password": "<password>"}'curl --fail --request GET \
--url 'https://channels.chattigo.com/bsp-cloud-chattigo-isv/{version}/5492646715777/media/980301304349102' \
--header 'Authorization: <JWT>' \
--output audio.oggIf you want to release that media at Meta, you delete it with DELETE /{version}/{did}/media/{media_id}.
Uploading a file and using it in a template
- Request the token with
POST /login. - Upload the file with
POST /{version}/{did}/media, multipart with thefilepart. - The response is
{"h": "4::..."}. That value is theheader_handleof the media header when you create or edit a template, as explained in Templates.
curl --request POST \
--url 'https://channels.chattigo.com/bsp-cloud-chattigo-isv/{version}/{did}/media' \
--header 'Authorization: <JWT>' \
-F 'file=@/path/file.jpg'To send media inside a message you use a public URL in link, not the handle: see Messages.
Download media
GET /{version}/{did}/media/{media_id}
Downloads the binary body of a WhatsApp media file with the channel first and the media identifier last. It returns the media Content-Type, the real size of the body and a suggested filename.
Parameters
Path
| Parameter | Type | Required | Description |
|---|---|---|---|
version | String | Yes | Meta’s Graph API version. Forwarded as-is. |
did | String | Yes | Channel identifier. |
media_id | String | Yes | Media identifier. |
Identifiers in the webhook
| Value | Origin in the inbound payload |
|---|---|
media_id | entry[].changes[].value.messages[].audio.id (same for image.id, document.id, video.id and sticker.id). |
did | entry[].changes[].value.metadata.display_phone_number, the same value the documentation calls waId in PATCH /webhooks/inbound. |
The channel identifier this API uses is the phone number, not Meta’s phone_number_id.
/{version}/{did}/media/{media_id}. It is the same order as the delete route and the rest of the channel API.Request
GET /{version}/{did}/media/{media_id}
curl --fail --request GET \
--url 'https://channels.chattigo.com/bsp-cloud-chattigo-isv/{version}/{did}/media/{media_id}' \
--header 'Authorization: <JWT>' \
--output audio.oggThe --output audio.ogg in the example is the name you save it with, not something you need to know in advance; the extension can be derived from the response Content-Type. --fail prevents an error body from being saved as if it were the media. If you prefer curl to use the name the response suggests, replace --output audio.ogg with --remote-header-name --remote-name.
For example, this inbound webhook shows the fields this route uses for the audio message:
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "102290129340398",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "5492646715777",
"phone_number_id": "106540352242922"
},
"messages": [
{
"from": "5491122334455",
"id": "wamid.HBgNNTQ5MjY0NjcxNTc3NxUCABIYFj",
"timestamp": "1758552655",
"type": "audio",
"audio": {
"id": "980301304349102",
"mime_type": "audio/ogg; codecs=opus"
}
}
]
}
}
]
}
]
}the download uses did = 5492646715777 and media_id = 980301304349102:
curl --fail --request GET \
--url 'https://channels.chattigo.com/bsp-cloud-chattigo-isv/{version}/5492646715777/media/980301304349102' \
--header 'Authorization: <JWT>' \
--output audio.oggResponse
Success (200)
Returns the binary media body with the headers:
| Header | Description |
|---|---|
Content-Type | Media type that Meta reports for the file. |
Content-Length | Real size of the response body. |
Content-Disposition | inline; filename="<media_id>.<ext>", where the extension is derived from the served type. |
Extension examples per served type:
| Served type | Extension |
|---|---|
audio/ogg | .ogg |
audio/amr | .amr |
audio/mpeg | .mp3 |
audio/mp4 | .m4a |
video/mp4 | .mp4 |
image/jpeg | .jpg |
application/pdf | .pdf |
Content-Disposition is a response header: it is never sent and it never needs to be known in advance, because the URL only needs the media identifier. It exists so a client that wants to save the file with the suggested name can use it; if you prefer to choose the name yourself, ignore it and rely on Content-Type, which is the essential piece of information.
Errors
| Code | Case |
|---|---|
401 | Invalid token or insufficient permissions. |
| Meta | Meta’s errors are forwarded as-is, with its status code and body. For a media that does not exist, expired or is not accessible, Meta answers 404 with Unsupported get request. Object with ID '<media_id>' does not exist, cannot be loaded due to missing permissions, or does not support this operation.... |
404 with the plain-text body 404 page not found does not come from Meta: it means the URL does not match any registered route and no handler processed the request. Check that the path shape is /{version}/{did}/media/{media_id}.Upload media
POST /{version}/{did}/media
Uploads a file to Meta with the channel first. The file type sent to Meta is the one declared by the file part of the multipart when Meta accepts it, and is inferred from the content otherwise. The result is the file handle used as the header_handle of a media header in a template, as explained in Templates.
Parameters
Path
| Parameter | Type | Required | Description |
|---|---|---|---|
version | String | Yes | Meta’s Graph API version. Forwarded as-is. |
did | String | Yes | Channel identifier. |
Body (multipart/form-data)
| Field | Type | Required | Description |
|---|---|---|---|
file | File | Yes | File to upload. |
Request
POST /{version}/{did}/media
curl --request POST \
--url 'https://channels.chattigo.com/bsp-cloud-chattigo-isv/{version}/{did}/media' \
--header 'Authorization: <JWT>' \
-F 'file=@/path/file.jpg'Response
Success (200)
{
"h": "4::aW1hZ2............."
}Types accepted by Meta
| Group | Types |
|---|---|
| Audio | audio/aac, audio/amr, audio/mpeg, audio/mp4, audio/ogg |
| Images | image/jpeg, image/png |
| Stickers | image/webp |
| Videos | video/mp4, video/3gpp |
| Documents | text/plain, application/pdf, application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.openxmlformats-officedocument.presentationml.presentation |
Errors
| Code | Case |
|---|---|
400 | The file part is missing from the multipart form: {"message":"file require"}. |
401 | Invalid token or insufficient permissions. |
| Meta | Meta’s errors are forwarded as-is, with its status code and body. |
Delete media
Deletion already uses the channel first: DELETE /{version}/{did}/media/{media_id}, documented on the Media page.