Mídia no webhook do WhatsApp: como receber, baixar e os limites de tamanho por canal
O webhook traz a referência da mídia, não o arquivo. Como baixar imagem, áudio, vídeo e documento pela API, em base64 ou binário, e os limites de tamanho de WhatsApp, Instagram e Messenger.
O webhook não traz o arquivo. Traz a referência da mídia, e o arquivo você baixa com um GET em /{key}/message/{messageId}/media, em base64 ou em binário. Imagem, áudio, vídeo, documento e figurinha funcionam do mesmo jeito. O que varia é o canal: no WhatsApp você baixa pela API da instância, e no Instagram e no Messenger baixa direto do CDN da Meta. Cada canal também tem o próprio limite de tamanho.
Este guia usa o webhook no formato meta, o mesmo envelope para os três canais. Se ainda não configurou, comece por um webhook para WhatsApp, Instagram e Messenger.
O que chega no webhook quando o cliente manda uma mídia
A mensagem de mídia chega com type igual ao tipo de arquivo (image, video, audio, document ou sticker) e um bloco com o mesmo nome. Veja um documento recebido pelo WhatsApp:
{
"from": "5511988887777",
"id": "WAMID_DOC",
"timestamp": "1700000000",
"type": "document",
"document": {
"id": "WAMID_DOC",
"url": "https://us.api-wa.me/YOUR_KEY/message/WAMID_DOC/media",
"mime_type": "application/pdf",
"sha256": "YWJj",
"filename": "orcamento.pdf",
"caption": "Segue o orçamento"
}
}Os campos do bloco mudam um pouco conforme o tipo:
| Tipo | Campos |
|---|---|
image, video | id, url, mime_type, sha256, caption |
audio | id, url, mime_type, sha256, voice |
document | id, url, mime_type, sha256, filename, caption |
sticker | id, url, mime_type, sha256, animated |
Três deles fazem diferença na integração:
urljá aponta para o endpoint de download da sua instância, com a mensagem certa no caminho. No WhatsApp você não precisa montar a URL.filenamesó existe emdocumente guarda o nome original do arquivo. Guarde esse valor, porque o download não o devolve (mais sobre isso abaixo).sha256é o hash SHA-256 do arquivo. Serve para conferir a integridade depois de baixar e para reconhecer o mesmo arquivo enviado duas vezes.
O evento não traz tamanho do arquivo. Para saber o tamanho antes de ler o corpo, use o Content-Length da resposta em binário.
Baixando pelo endpoint: json ou binary
O endpoint é um só, e o parâmetro format escolhe a forma da resposta.
format=json (o padrão)
curl "https://us.api-wa.me/YOUR_KEY/message/WAMID_IMG/media"{
"messageId": "3EB05344D9E03CFCA3A09B",
"mimetype": "image/jpeg",
"base64": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."
}O campo base64 não traz só o base64: ele vem como data URL, com o prefixo data:<mimetype>;base64, na frente. Algumas APIs aceitam a data URL direto, e para essas basta repassar o campo. Se o destino espera base64 puro, corte tudo até a primeira vírgula, e não pelo ; do prefixo, porque o mimetype de áudio de voz é audio/ogg; codecs=opus e tem um ; no meio:
const puro = json.base64.slice(json.base64.indexOf(",") + 1);format=binary
curl -o arquivo "https://us.api-wa.me/YOUR_KEY/message/WAMID_IMG/media?format=binary"A resposta é o arquivo, com Content-Type igual ao mimetype, Content-Length e Content-Disposition: attachment. Aberta no navegador, a URL baixa o arquivo direto. Para guardar a mídia, prefira binary: o base64 ocupa cerca de um terço a mais, e no seu servidor é preciso decodificar de volta antes de salvar.
O nome que vem no Content-Disposition é <messageId>.<extensão>, com a extensão tirada do mimetype (image/jpeg vira .jpeg). O nome original do documento não vem no download. Se o nome importa, e em documento quase sempre importa, pegue o filename do webhook.
Qualquer valor de format que não seja binary devolve JSON.
O handler completo
Juntando tudo, este handler recebe o evento, baixa a mídia de WhatsApp em binário e salva com o nome certo:
import express from "express";
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
const TIPOS_MIDIA = ["image", "video", "audio", "document", "sticker"];
const app = express();
app.use(express.json());
app.post("/webhook/wame", async (req, res) => {
res.sendStatus(200); // confirme antes de baixar: download leva tempo
const body = req.body;
const msg = body?.entry?.[0]?.changes?.[0]?.value?.messages?.[0];
if (!msg || !TIPOS_MIDIA.includes(msg.type)) return;
const midia = msg[msg.type];
try {
if (body.provider === "whatsapp") {
await baixarWhatsApp(msg.id, midia);
} else {
await baixarCdnMeta(msg.id, midia); // Instagram e Messenger, veja abaixo
}
} catch (e) {
console.error("falha ao baixar mídia", msg.id, e);
}
});
async function baixarWhatsApp(messageId, midia) {
const r = await fetch(`${midia.url}?format=binary`);
if (!r.ok) throw new Error(`download ${r.status}`);
const buffer = Buffer.from(await r.arrayBuffer());
// Confere se chegou o arquivo que o evento descreveu
// (aceita o hash em base64 ou em hex)
const hash = createHash("sha256").update(buffer).digest();
const confere = [hash.toString("base64"), hash.toString("hex")].includes(midia.sha256);
if (midia.sha256 && !confere) throw new Error("sha256 não confere");
const ext = midia.mime_type.split("/")[1].split(";")[0];
const nome = midia.filename ?? `${messageId}.${ext}`;
await writeFile(`./midias/${nome}`, buffer);
}Em produção, troque o writeFile pelo seu storage (S3, R2, GCS) e jogue o download numa fila em vez de rodar dentro do handler. Não confie no filename como caminho: ele vem do cliente e pode trazer ../. O mesmo evento também pode chegar duas vezes, e a solução para isso está em idempotência e retry de webhook.
Com os SDKs oficiais, o download é uma linha. Em Node, wa.message.getMedia(messageId, "binary") (SDK JavaScript/TypeScript). Em PHP, $wa->message->downloadMedia($messageId, 'binary') (SDK PHP).
Instagram e Messenger: o arquivo vem do CDN da Meta
Nesses dois canais, o bloco de mídia chega mais enxuto:
"image": {
"url": "https://cdn.instagram.com/img.jpg",
"mime_type": "image/jpeg",
"caption": "foto"
}O bloco não tem id nem sha256, e a url é do CDN da própria Meta, não da API da instância. O endpoint /message/{messageId}/media atende mensagens de WhatsApp. Para Instagram e Messenger, baixe da url:
async function baixarCdnMeta(messageId, midia) {
const r = await fetch(midia.url);
if (!r.ok) throw new Error(`cdn ${r.status}`);
const buffer = Buffer.from(await r.arrayBuffer());
const ext = midia.mime_type.split("/")[1];
await writeFile(`./midias/${messageId}.${ext}`, buffer);
}Duas diferenças mudam o código:
- O
mime_typeaqui é deduzido da extensão da URL, não informado pela Meta. Se o tipo exato importa (converter áudio, validar formato), confira oContent-Typeda resposta do CDN. - Reel ou post compartilhado não traz um arquivo. A
urlé o link da publicação, uma página HTML. Trate como link e não tente salvar como imagem.
Limites de tamanho por canal
Estes são os limites que a Meta documenta para envio de mídia por API:
| Canal | Imagem | Vídeo | Áudio | Documento |
|---|---|---|---|---|
| 5 MB | 16 MB | 16 MB | 100 MB | |
| 8 MB | 25 MB | 25 MB | não aceita | |
| Messenger | 25 MB | 25 MB | 25 MB | 25 MB |
Figurinha no WhatsApp é webp, com até 100 KB (estática) ou 500 KB (animada).
Na hora de receber, esses números não mudam o seu código: o que chega é o que o app do cliente deixou enviar, e você baixa do jeito descrito acima. Eles importam quando você devolve ou repassa o arquivo, e o caso típico é o atendimento omnichannel. Um vídeo de 20 MB recebido no Messenger não sai pelo WhatsApp, porque passa dos 16 MB. Um PDF recebido no WhatsApp não sai pelo Instagram, que não aceita documento. Cheque o tamanho (Content-Length ou buffer.length) e o tipo antes de reenviar. Quando o arquivo não couber, mande um link para ele no seu storage em vez de mandar o arquivo.
O áudio a WAME já valida no envio: acima de 16 MB no WhatsApp ou de 25 MB no Instagram e no Messenger, o envio volta com erro em vez de falhar do lado da Meta.
O que quebra em produção
Deixar o download para depois. A mídia fica nos servidores do WhatsApp e da Meta, e eles descartam arquivo antigo. Um download feito dias depois pode voltar 404 com Media expired or unavailable on WhatsApp servers. Se o arquivo vai ser usado depois (anexo de ticket, comprovante, histórico do CRM), baixe quando o evento chegar e guarde no seu storage.
Tratar todo erro do mesmo jeito. O endpoint devolve 404 com uma mensagem que diz o motivo: Message not found (o id não existe para essa instância), Media not available or message is not a media message (a mensagem não é mídia) ou mídia expirada. Nenhum desses se resolve tentando de novo. Uma falha 5xx pode ser passageira: tente de novo poucas vezes, com espera entre as tentativas, e depois desista e registre.
Baixar tudo de uma vez. O endpoint passa pelo rate limit da instância. Um backfill de mil mensagens em Promise.all estoura o limite e metade volta com erro. Use uma fila com concorrência baixa, como num disparo.
Expor a URL de download. A url que chega no WhatsApp leva a key da instância no caminho. Não mande essa URL para o front-end, não grave em log aberto e não passe para terceiros. Quem tiver a URL tem a sua chave. O front-end deve receber a URL do arquivo no seu storage. Mais sobre isso em segurança de token e webhook.
Confiar no mimetype para escolher o que fazer. O áudio de voz do WhatsApp chega como audio/ogg; codecs=opus, e um mime_type === "audio/ogg" falha. Compare o prefixo (startsWith("audio/")) ou use o type da mensagem. Para voz, o bloco audio traz voice: true, e é o caminho para transcrever o áudio com IA.
Conclusão
Mídia no webhook dá dois passos: o evento avisa que chegou um arquivo e diz onde ele está, e o seu código busca o arquivo. No WhatsApp a busca é pelo endpoint da instância, em binário para guardar ou em JSON quando o destino espera base64. No Instagram e no Messenger é pela url do CDN da Meta. Baixe na chegada, guarde o filename do evento, confira o sha256 e cheque tamanho e tipo antes de repassar o arquivo para outro canal. Assim a mídia não se perde nem trava o seu fluxo.
Pronto para automatizar seu WhatsApp?
Crie sua conta gratuita e comece a enviar mensagens pela API em minutos.
Começar grátisPerguntas frequentes
O webhook do WhatsApp manda o arquivo junto com a mensagem?+
Não. O evento traz só a referência da mídia: o id da mensagem, o mime_type, o sha256, a legenda e, em documentos, o nome original do arquivo. O conteúdo você baixa à parte, pelo GET /{key}/message/{messageId}/media. Assim o webhook fica leve e chega rápido, mesmo quando o cliente manda um vídeo.
Qual a diferença entre format=json e format=binary no download?+
Sem parâmetro, ou com format=json, a resposta é um JSON com messageId, mimetype e base64, sendo que o base64 vem como data URL (data:image/jpeg;base64,...). Com format=binary, a resposta é o próprio arquivo, com Content-Type e Content-Length. Use binary para salvar em disco ou storage e para repassar a outra API. Use json quando o destino já espera base64.
Como baixo mídia recebida pelo Instagram ou pelo Messenger?+
Nesses canais o bloco de mídia chega sem id e traz uma url do CDN da Meta. Você baixa o arquivo direto dessa url, e não pelo endpoint de mídia da instância, que atende mensagens de WhatsApp. Em reels e posts compartilhados a url é o link da publicação, não um arquivo para baixar.
Qual o tamanho máximo de arquivo no WhatsApp, Instagram e Messenger?+
Pela documentação da Meta: no WhatsApp são 5 MB para imagem, 16 MB para vídeo e áudio e 100 MB para documento. No Instagram, 8 MB para imagem e 25 MB para vídeo e áudio. No Messenger, 25 MB para qualquer anexo. Os limites valem para envio pela API, então pesam quando o seu fluxo reenvia por um canal um arquivo recebido em outro.
Por quanto tempo a mídia fica disponível para download?+
Não conte com um prazo fixo. O arquivo depende dos servidores do WhatsApp e da Meta, que descartam mídia antiga, e um download tardio pode voltar 404 com a mensagem de mídia expirada. O mais seguro é baixar assim que o evento chega e guardar o arquivo no seu próprio storage.
Continue lendo
Um webhook para WhatsApp, Instagram e Messenger: o padrão Meta na prática
Receba WhatsApp, Instagram e Messenger em um único webhook no formato da Meta Cloud API, reusando um só parser. O campo provider diz o canal.
Transcrição de áudio no WhatsApp com IA: responder mensagens de voz automaticamente
Como baixar o áudio recebido no WhatsApp pela API, transcrever com IA (Whisper) e responder — o código completo, com o que quebra em produção.
Bloqueios em massa do WhatsApp: o que aconteceu e quem foi atingido
A onda de bloqueios de contas do WhatsApp em agosto de 2026 atingiu negócios inteiros. O que os casos tinham em comum, por que a Meta agiu em lote e quem passou ileso.