Como receber e baixar áudio, imagem e PDF do WhatsApp pelo webhook
Para baixar áudio, imagem ou PDF recebido no WhatsApp, o webhook avisa que a mensagem chegou e você chama POST /api/v1/{instanceId}/message/download com o campo id da mensagem. A API devolve a URL do arquivo (fileURL) e o mimetype, ou o conteúdo em base64 se você pedir. Abaixo está o fluxo inteiro: configurar o evento, identificar o tipo, baixar, guardar e, se precisar, transcrever.
Publicado em · 7 min de leitura
Como funciona o fluxo de mídia recebida
São três passos. Um: o evento messages chega ao seu webhook com a mensagem; quando há mídia, o evento descreve o tipo e traz o ID da mensagem. Dois: seu servidor chama /message/download com esse id. Três: você copia o arquivo para o seu armazenamento e processa.
A separação existe por motivo prático: o webhook precisa ser leve e responder 2xx rápido, e arquivo é pesado. Por isso o webhook entrega o aviso, e o download acontece no seu worker, fora do caminho da resposta HTTP. Para o desenho geral do webhook, veja o guia de webhook em produção.
Como configurar o webhook para receber mensagens
Registre a URL pública (HTTPS) da instância com POST em /webhook, enviando url, enabled e a lista events. Os eventos vêm desativados por padrão; para mídia, ative messages. Eventos de alto volume como presence e history só ligue se for tratá-los.
Teste a entrega com POST /instances/{id}/webhook/test: ele manda um evento de teste e informa o status HTTP e o tempo de resposta do seu endpoint. Se algo não chegar, GET /instances/{id}/webhook/deliveries mostra as últimas entregas e GET /webhook/errors lista as falhas.
cURL
curl -X POST https://api.zapzapapi.com/api/v1/SEU_INSTANCE_ID/webhook \
-H "Content-Type: application/json" \
-H "x-api-key: SUA_API_KEY" \
-H "x-api-secret: SEU_API_SECRET" \
-d '{"url":"https://seu-servidor.com/webhook/whatsapp","enabled":true,"events":["messages"]}'O que olhar no evento para saber se é áudio, imagem ou PDF
A forma exata do payload deve ser conferida num evento real da sua instância: grave o corpo cru em log, envie do celular uma foto, um áudio e um PDF e compare. Os nomes de campo variam conforme o tipo de mensagem, e é melhor mapear a partir do que você viu do que de um exemplo genérico.
O que você precisa extrair em todos os casos: o ID da mensagem, o tipo (texto, imagem, áudio, documento), o remetente e se a mensagem foi enviada por você (fromMe). Descarte fromMe logo no início. Os exemplos abaixo usam leitura defensiva com valores alternativos; ajuste os nomes depois de ver o seu payload.
Como baixar a mídia com /message/download em Node.js
A chamada recebe id e flags opcionais. Com return_link true (padrão), a resposta traz fileURL e mimetype. Com return_base64 true, traz o conteúdo em base64 na própria resposta; use só para arquivos pequenos. generate_mp3 converte áudio para MP3 e transcribe pede a transcrição, que a documentação descreve como best-effort e pode não vir.
O servidor abaixo responde 200 antes de baixar, extrai o ID, chama o download e grava o arquivo em disco. Em produção, troque a gravação em disco por upload para o seu armazenamento de objetos e não trate a fileURL como permanente: copie o arquivo para o seu lado.
Node.js
import express from "express";
import { writeFile } from "node:fs/promises";
const app = express();
app.use(express.json({ limit: "2mb" }));
const BASE = "https://api.zapzapapi.com/api/v1/" + process.env.ZAPZAP_INSTANCE_ID;
const headers = {
"Content-Type": "application/json",
"x-api-key": process.env.ZAPZAP_API_KEY,
"x-api-secret": process.env.ZAPZAP_API_SECRET,
};
app.post("/webhook/whatsapp", (req, res) => {
res.sendStatus(200);
tratar(req.body).catch((e) => console.error("handler error", e));
});
async function tratar(evento) {
const msg = evento?.message ?? evento?.data ?? evento;
if (msg?.fromMe) return;
const id = msg?.messageid ?? msg?.id;
const tipo = String(msg?.messageType ?? msg?.type ?? "");
if (!id || !/image|audio|ptt|document|video/i.test(tipo)) return;
const r = await fetch(BASE + "/message/download", {
method: "POST",
headers,
body: JSON.stringify({ id, return_link: true }),
});
if (!r.ok) throw new Error("download failed " + r.status + ": " + (await r.text()));
const { fileURL, mimetype } = await r.json();
const arquivo = await fetch(fileURL);
const bytes = Buffer.from(await arquivo.arrayBuffer());
const ext = (mimetype || "application/octet-stream").split("/")[1]?.split(";")[0] || "bin";
await writeFile("./midia-" + id.replace(/[^a-zA-Z0-9_-]/g, "_") + "." + ext, bytes);
}
app.listen(3000);Como baixar a mídia com /message/download em Python
A mesma lógica em Python com Flask e requests. O ponto de atenção é o mesmo: responder rápido e baixar em segundo plano. O exemplo usa uma thread simples para ser curto; em produção use uma fila (Celery, RQ, Cloud Tasks ou equivalente).
Se você só precisa do texto de um áudio, peça transcribe true na chamada de download, sabendo que é best-effort, ou use o módulo de IA da instância para transcrição garantida, conforme a própria referência do endpoint.
Python
import os
import threading
import requests
from flask import Flask, request
app = Flask(__name__)
BASE = "https://api.zapzapapi.com/api/v1/" + os.environ["ZAPZAP_INSTANCE_ID"]
HEADERS = {
"Content-Type": "application/json",
"x-api-key": os.environ["ZAPZAP_API_KEY"],
"x-api-secret": os.environ["ZAPZAP_API_SECRET"],
}
def tratar(evento):
msg = evento.get("message") or evento.get("data") or evento
if msg.get("fromMe"):
return
msg_id = msg.get("messageid") or msg.get("id")
tipo = str(msg.get("messageType") or msg.get("type") or "")
if not msg_id or not any(t in tipo.lower() for t in ("image", "audio", "ptt", "document", "video")):
return
r = requests.post(BASE + "/message/download", json={"id": msg_id, "return_link": True}, headers=HEADERS, timeout=60)
r.raise_for_status()
dados = r.json()
arquivo = requests.get(dados["fileURL"], timeout=60)
arquivo.raise_for_status()
ext = dados.get("mimetype", "application/octet-stream").split("/")[-1].split(";")[0]
with open("midia-%s.%s" % (str(msg_id).replace(":", "_"), ext), "wb") as f:
f.write(arquivo.content)
@app.post("/webhook/whatsapp")
def webhook():
threading.Thread(target=tratar, args=(request.get_json(force=True),), daemon=True).start()
return "", 200Como localizar uma mensagem antiga e baixar a mídia dela
Se você perdeu o evento (servidor fora do ar, por exemplo), recupere a mensagem com POST /message/find. Filtre por id para uma mensagem específica ou por chatid para o histórico de uma conversa, com limit e offset para paginar. Depois use o id retornado em /message/download.
Isso também resolve o caso de reprocessamento: se o download falhou, o id ficou guardado e você repete a chamada depois, sem depender de novo evento do webhook.
cURL
curl -X POST https://api.zapzapapi.com/api/v1/SEU_INSTANCE_ID/message/find \
-H "Content-Type: application/json" \
-H "x-api-key: SUA_API_KEY" \
-H "x-api-secret: SEU_API_SECRET" \
-d '{"chatid":"5511999999999@s.whatsapp.net","limit":20,"offset":0}'Qual formato escolher: link, base64, MP3 ou transcrição
| Necessidade | Parâmetro | Observação |
|---|---|---|
| Guardar o arquivo no seu storage | return_link true | Baixe da fileURL e copie para o seu lado |
| Arquivo pequeno direto na resposta | return_base64 true | Evite para arquivos grandes |
| Tocar áudio em navegador ou converter | generate_mp3 true | Devolve o áudio em MP3 |
| Texto do áudio | transcribe true | Best-effort, pode não retornar |
| Transcrição garantida | Módulo de IA da instância | Ver a referência do endpoint |
Na dúvida, comece por return_link e trate o resto como otimização. O PDF e a imagem não precisam de conversão; o áudio de voz costuma ser o único que pede MP3 para tocar em qualquer lugar.
Como desenhar a fila entre o webhook e o worker de download
Em produção, o endpoint do webhook faz três coisas e só três: valida o formato do evento, grava o id da mensagem e o tipo em uma fila (banco, Redis, fila gerenciada) e responde 2xx. Um worker separado consome a fila, chama /message/download, copia o arquivo para o seu armazenamento e marca o item como concluído.
Esse desenho resolve três problemas de uma vez. Falha de download vira retry com backoff sem perder o evento. Pico de mensagens (uma campanha que gera muitas respostas) não derruba o endpoint. E a deduplicação fica simples: o id da mensagem é a chave única da fila, e um segundo evento igual é ignorado.
| Etapa | Onde roda | Se falhar |
|---|---|---|
| Receber o evento | Endpoint do webhook | A API reenvia ou você consulta GET /webhook/errors |
| Gravar na fila | Endpoint do webhook | Responder 5xx para provocar reentrega |
| Baixar a mídia | Worker | Retry com backoff; guardar o id para tentar depois |
| Copiar para o storage | Worker | Repetir; a URL de origem pode expirar, então não demore |
| Processar (OCR, transcrição, IA) | Worker | Marcar como erro e alertar |
Boas práticas: idempotência, segurança e privacidade
Idempotência: o mesmo evento pode chegar duas vezes. Guarde o id da mensagem e ignore ids já processados, para não baixar e processar o mesmo arquivo em dobro. Segurança: trate todo arquivo recebido como não confiável. Valide o tipo real do arquivo (não só a extensão), limite o tamanho, nunca execute nem abra documentos no servidor sem antivírus ou isolamento.
Privacidade: áudio, foto e PDF de clientes são dados pessoais e podem ser sensíveis. Defina por quanto tempo guarda, quem acessa e como apaga, e registre a base legal conforme a LGPD. O guia de disparo e LGPD traz o raciocínio geral. Para depurar a entrega do webhook, consulte a documentação de webhooks e a referência de message/download.
Resumo rápido
- O webhook avisa que chegou mídia; o arquivo vem de POST /message/download com o id da mensagem.
- Ative o evento messages em POST /webhook e responda 2xx antes de baixar.
- Use return_link para guardar o arquivo; base64 só para arquivos pequenos; transcribe é best-effort.
- POST /message/find recupera mensagens perdidas por id ou por chatid.
- Trate o arquivo como não confiável, guarde o id para idempotência e defina retenção por causa da LGPD.
Continue lendo
Perguntas frequentes
Como baixar uma imagem recebida no WhatsApp pela API?
Pegue o ID da mensagem no evento do webhook e chame POST /message/download com id e return_link true. A resposta traz fileURL e mimetype; baixe o arquivo dessa URL e copie para o seu armazenamento.
O webhook já envia o arquivo junto?
O evento avisa a chegada e descreve a mensagem; o arquivo em si vem pela chamada de download. Isso mantém o webhook leve e permite baixar fora do caminho da resposta HTTP.
Como transcrever um áudio recebido?
A chamada de download aceita transcribe true, mas a referência descreve como best-effort: pode não retornar. Para transcrição garantida, use o módulo de IA da instância.
Qual o limite de tamanho do arquivo?
Para envio, a referência indica cerca de 190 MB para imagem, vídeo e áudio e cerca de 500 MB para documento quando enviados por URL, e cerca de 2 MB quando em base64. Para o download, prefira return_link em vez de base64.
A fileURL é permanente?
Não assuma isso. Copie o arquivo para o seu armazenamento assim que baixar. Se precisar do arquivo depois, guarde o id da mensagem e chame o download de novo.
Receba mídia do WhatsApp no seu sistema
Crie a conta, verifique o telefone e receba o crédito de boas-vindas, sem depositar (um por telefone, sujeito à campanha vigente). Conecte por QR code, ative o evento messages e teste com uma foto.
