Mídia
Há duas formas de mandar um arquivo, e a escolha entre elas muda bastante o desempenho.
Por link
A Meta baixa o arquivo do seu servidor a cada envio.
curl -X POST https://api.joinotify.com/v1/106540352242922/messages \n -H 'Authorization: Bearer sk_live_xxx' \n -H 'Content-Type: application/json' \n -d '{
"messaging_product": "whatsapp",
"to": "5541987111527",
"type": "image",
"image": { "link": "https://exemplo.com/catalogo.png" }
}'
Bom para arquivo único, gerado sob demanda — um boleto, uma nota fiscal. A URL precisa ser
pública, com Content-Type correto e sem redirecionamento para página de login.
Por id de mídia
Você envia o arquivo uma vez e reutiliza o id.
curl -X POST https://api.joinotify.com/v1/106540352242922/media \
-H 'Authorization: Bearer sk_live_xxx' \
-F 'messaging_product=whatsapp' \
{ "id": "1234567890" }
curl -X POST https://api.joinotify.com/v1/106540352242922/messages \n -H 'Authorization: Bearer sk_live_xxx' \n -H 'Content-Type: application/json' \n -d '{
"messaging_product": "whatsapp",
"to": "5541987111527",
"type": "image",
"image": { "id": "1234567890" }
}'
Para disparo em massa, sempre o id da mídia. Com link, mil envios são mil downloads no
seu servidor. O arquivo fica disponível na Meta por 30 dias.
O upload tem limite próprio: 60 requisições por minuto.
Limites por tipo
| Tipo | Formatos aceitos | Tamanho |
|---|---|---|
| Imagem | JPEG, PNG | 5 MB |
| Vídeo | MP4, 3GPP — H.264 + AAC | 16 MB |
| Áudio | AAC, AMR, MP3, MP4 Audio, OGG (só OPUS) | 16 MB |
| Documento | qualquer | 100 MB |
| Figurinha | WebP | 100 KB estática · 500 KB animada |
Vídeo com outro codec é aceito no upload e falha na entrega, sem erro claro. Se um vídeo "some", confira o codec antes de qualquer outra coisa.
Recebendo mídia
Quando o cliente manda um arquivo, o webhook traz apenas o id — não o conteúdo:
{
"messages": [
{
"from": "5541999999999",
"id": "wamid.HBgN...",
"type": "image",
"image": { "id": "9876543210", "mime_type": "image/jpeg", "sha256": "..." }
}
]
}
Para baixar, são dois passos.
curl https://api.joinotify.com/v1/9876543210 \
-H 'Authorization: Bearer sk_live_xxx'
{
"url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=...",
"mime_type": "image/jpeg",
"file_size": 184320,
"id": "9876543210"
}
curl -L 'https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=...' \
-H 'Authorization: Bearer sk_live_xxx' \
-o recebido.jpg
Abrir o link no navegador devolve erro, e colocá-lo num <img src> não funciona. Ele só
responde com o header Authorization.
Não guarde essa URL no seu banco como se fosse permanente: ela expira. Baixe o arquivo, salve no seu próprio storage e guarde a sua URL.
Fluxo recomendado ao receber
async function baixarMidia(mediaId) {
const headers = { Authorization: `Bearer ${process.env.JOINOTIFY_TOKEN}` };
const meta = await fetch(`https://api.joinotify.com/v1/${mediaId}`, { headers });
const { url, mime_type } = await meta.json();
const arquivo = await fetch(url, { headers });
const buffer = Buffer.from(await arquivo.arrayBuffer());
// Guarde no seu storage e devolva a SUA URL.
return storage.salvar(buffer, { mimeType: mime_type });
}
Faça isso de forma assíncrona, fora do handler do webhook — o seu endpoint precisa
responder 2xx em poucos segundos, ou a entrega é considerada falha e reenviada.
Apagando
curl -X DELETE https://api.joinotify.com/v1/1234567890 \
-H 'Authorization: Bearer sk_live_xxx'
Mensagens já entregues com aquele arquivo continuam funcionando para o cliente. Apagar só libera o armazenamento e impede novos envios com aquele id.