Raphael Serafim· Publicado em 25 de setembro de 2026· 8 min de leitura

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.

Ver como Markdown

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:

json
{
  "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:

TipoCampos
image, videoid, url, mime_type, sha256, caption
audioid, url, mime_type, sha256, voice
documentid, url, mime_type, sha256, filename, caption
stickerid, url, mime_type, sha256, animated

Três deles fazem diferença na integração:

  • url já 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.
  • filename só existe em document e 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)

bash
curl "https://us.api-wa.me/YOUR_KEY/message/WAMID_IMG/media"
json
{
  "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:

javascript
const puro = json.base64.slice(json.base64.indexOf(",") + 1);

format=binary

bash
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:

javascript
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:

json
"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:

javascript
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_type aqui é deduzido da extensão da URL, não informado pela Meta. Se o tipo exato importa (converter áudio, validar formato), confira o Content-Type da 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:

CanalImagemVídeoÁudioDocumento
WhatsApp5 MB16 MB16 MB100 MB
Instagram8 MB25 MB25 MBnão aceita
Messenger25 MB25 MB25 MB25 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átis

Perguntas 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