← Blog

·

Como receber e baixar a mídia que o cliente envia pela API do WhatsApp

Atualizado em 28/10/2026

Quando o cliente manda uma foto, um PDF, um áudio ou um vídeo, o webhook da API do WhatsApp não traz o arquivo: traz um media id. Para baixar, você faz GET /{media-id} para obter uma URL temporária, que expira em 5 minutos, e depois faz um GET nessa URL com o seu token de acesso no cabeçalho Authorization. Como o media id recebido por webhook vale só 7 dias, o certo é baixar logo e guardar o arquivo no seu próprio storage.

Este guia mostra o fluxo completo, os campos de cada tipo de mídia, os limites de tamanho, como conferir a integridade do arquivo e exemplos em cURL, Node.js e Python, incluindo o envio para o Amazon S3.

O fluxo em 4 passos

  1. Webhook: chega uma mensagem com type igual a image, document, audio, video ou sticker, com o id da mídia.
  2. URL temporária: GET https://graph.facebook.com/v26.0/{media-id} devolve url, mime_type, sha256 e file_size.
  3. Download: GET na url com Authorization: Bearer <token>. Sem o token, a requisição falha.
  4. Armazenamento: salve no seu storage (S3, GCS, disco) e grave no banco o caminho do arquivo, nunca a URL da Meta.

Os prazos vêm da documentação de mídia da Meta: URLs de mídia expiram em 5 minutos (depois disso, basta consultar o media id de novo para gerar outra), media ids vindos de webhooks expiram em 7 dias e media ids gerados por upload expiram em 30 dias.

O que chega no webhook

Este é o formato de uma imagem recebida, segundo a referência de webhooks de imagem (valores de exemplo):

{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "WABA_ID",
    "changes": [{
      "field": "messages",
      "value": {
        "messaging_product": "whatsapp",
        "metadata": {
          "display_phone_number": "5511999999999",
          "phone_number_id": "PHONE_NUMBER_ID"
        },
        "contacts": [{ "profile": { "name": "Maria" }, "wa_id": "5511988887777" }],
        "messages": [{
          "from": "5511988887777",
          "id": "wamid.HBgLNTUx...",
          "timestamp": "1761650000",
          "type": "image",
          "image": {
            "caption": "Comprovante",
            "mime_type": "image/jpeg",
            "sha256": "SfInY0gGKTsJlUWbwxC1k+FAD0FZHvzwfpvO0zX0GUI=",
            "id": "1003383421387256",
            "url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=..."
          }
        }]
      }
    }]
  }]
}

O objeto muda de nome conforme o tipo (image, document, audio, video, sticker), mas a estrutura é parecida. Documentos trazem também filename, útil para salvar com o nome original.

Repare no campo url. A Meta começou a liberar esse campo no próprio webhook de forma gradual a partir de 12/11/2025, permitindo pular o passo 2 e baixar direto com o token. Como a liberação é gradual, seu código deve funcionar com e sem ele: use a url se vier e, se não vier ou o download der erro, consulte o media id.

CampoPara que serve
idMedia id; use para gerar a URL de download
mime_typeTipo do arquivo (image/jpeg, application/pdf, audio/ogg…)
sha256Hash do arquivo, para conferir integridade
captionLegenda enviada pelo cliente, quando houver
filenameNome original (documentos)
urlURL de download direto, em liberação gradual

Quer testar enquanto lê? Crie sua conta na Vectalk e envie a primeira mensagem pela API oficial. Criar conta grátis →

Tipos e limites de tamanho

A tabela abaixo resume os tipos suportados e os tamanhos máximos da página de mídia da Meta. Ela ajuda a dimensionar o storage e o tempo limite do download:

TipoFormatosTamanho máximo
ÁudioAAC, AMR, MP3, M4A, OGG (só codec OPUS, mono)16 MB
DocumentoTXT, XLS, XLSX, DOC, DOCX, PPT, PPTX, PDF100 MB
ImagemJPEG, PNG (8 bits, RGB ou RGBA)5 MB
FigurinhaWebP500 KB animada, 100 KB estática
Vídeo3GP, MP4 (H.264 e AAC)16 MB

Notas de voz chegam como mensagens do tipo audio. Se você vai passar o áudio para um serviço de transcrição, confira o mime_type recebido e se o serviço aceita aquele formato; se não aceitar, converta antes.

Exemplo com cURL

Passo 2, obter a URL:

curl "https://graph.facebook.com/v26.0/1003383421387256" \
  -H "Authorization: Bearer $TOKEN"

Resposta:

{
  "messaging_product": "whatsapp",
  "url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=...",
  "mime_type": "image/jpeg",
  "sha256": "SfInY0gGKTsJlUWbwxC1k+FAD0FZHvzwfpvO0zX0GUI=",
  "file_size": 303833,
  "id": "1003383421387256"
}

Passo 3, baixar:

curl -L "URL_RETORNADA" \
  -H "Authorization: Bearer $TOKEN" \
  -o comprovante.jpg

Opcionalmente, envie phone_number_id como parâmetro de consulta no passo 2: assim a Meta só processa a requisição se a mídia pertencer àquele número, uma proteção útil quando a mesma aplicação atende vários números.

Exemplo em Node.js

import crypto from 'node:crypto';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const GRAPH = 'https://graph.facebook.com/v26.0';
const TOKEN = process.env.WHATSAPP_TOKEN;
const s3 = new S3Client({ region: process.env.AWS_REGION });

async function obterUrl(mediaId) {
  const res = await fetch(`${GRAPH}/${mediaId}`, {
    headers: { Authorization: `Bearer ${TOKEN}` },
  });
  if (!res.ok) throw new Error(`media ${mediaId}: HTTP ${res.status}`);
  return res.json(); // { url, mime_type, sha256, file_size, id }
}

async function baixar(url) {
  const res = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}` } });
  if (!res.ok) throw Object.assign(new Error('download falhou'), { status: res.status });
  return Buffer.from(await res.arrayBuffer());
}

export async function salvarMidia(midia, chave) {
  let info = { url: midia.url, sha256: midia.sha256, mime_type: midia.mime_type };
  let arquivo;
  try {
    if (!info.url) throw new Error('sem url no webhook');
    arquivo = await baixar(info.url);
  } catch {
    info = await obterUrl(midia.id); // gera uma URL nova
    arquivo = await baixar(info.url);
  }

  const hash = crypto.createHash('sha256').update(arquivo).digest('base64');
  if (info.sha256 && hash !== info.sha256) throw new Error('hash não confere');

  await s3.send(new PutObjectCommand({
    Bucket: process.env.BUCKET,
    Key: chave,
    Body: arquivo,
    ContentType: info.mime_type,
  }));
  return chave;
}

O hash sha256 dos exemplos da Meta está em base64, por isso a comparação usa digest('base64'). Se no seu caso o formato vier diferente, registre os dois valores antes de ativar a checagem.

Exemplo em Python

import base64
import hashlib
import os

import boto3
import httpx

GRAPH = "https://graph.facebook.com/v26.0"
TOKEN = os.environ["WHATSAPP_TOKEN"]
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
s3 = boto3.client("s3")


def obter_url(client: httpx.Client, media_id: str) -> dict:
    r = client.get(f"{GRAPH}/{media_id}", headers=HEADERS)
    r.raise_for_status()
    return r.json()


def salvar_midia(midia: dict, chave: str) -> str:
    with httpx.Client(timeout=60, follow_redirects=True) as client:
        info = obter_url(client, midia["id"])
        r = client.get(info["url"], headers=HEADERS)
        if r.status_code == 404:  # URL vencida: gere outra e tente de novo
            info = obter_url(client, midia["id"])
            r = client.get(info["url"], headers=HEADERS)
        r.raise_for_status()

    calculado = base64.b64encode(hashlib.sha256(r.content).digest()).decode()
    if info.get("sha256") and calculado != info["sha256"]:
        raise ValueError("hash não confere")

    s3.put_object(
        Bucket=os.environ["BUCKET"],
        Key=chave,
        Body=r.content,
        ContentType=info["mime_type"],
    )
    return chave

Para documentos de até 100 MB, carregar tudo em memória pode pesar. Nesse caso, baixe em stream para um arquivo temporário e use upload_fileobj do boto3 ou o Upload do pacote @aws-sdk/lib-storage no Node, que fazem upload em partes.

Onde colocar isso na arquitetura

Não baixe a mídia dentro da requisição do webhook. O endpoint deve validar a assinatura, enfileirar o evento e responder 200 rápido; um worker separado faz o download e o upload. Assim, um vídeo grande não segura o webhook nem gera reenvios da Meta. Sobre a validação, veja como validar a assinatura do webhook.

Algumas boas práticas:

  • Chave de armazenamento previsível, por exemplo whatsapp/{phone_number_id}/{wa_id}/{wamid}.{ext}, para achar o arquivo pela mensagem.
  • Idempotência: antes de baixar, verifique se aquela mensagem já foi processada; o webhook pode chegar repetido.
  • Bucket privado e links assinados para quem precisa ver o arquivo, já que comprovantes e documentos têm dados pessoais. A LGPD (Lei 13.709/2018) exige cuidado com segurança e retenção desses dados; defina por quanto tempo guarda cada tipo. Isso não substitui a orientação de um advogado.
  • Retry com prazo: se o download falhar, você tem até 7 dias de validade do media id para tentar de novo.

Erros comuns

404 ao baixar. A página de mídia orienta gerar uma nova URL de mídia e tentar de novo; se continuar, renove o token de acesso. Na prática, a causa mais comum é usar uma URL guardada há mais de 5 minutos.

401 ou 403 no download. O GET na URL foi feito sem o cabeçalho Authorization, ou com um token que não tem acesso à conta. Use um token permanente de usuário do sistema; veja token permanente da API do WhatsApp.

Erro 131052 no webhook. A tabela de erros da Meta define como “não foi possível baixar a mídia enviada pelo usuário”. Leia o campo error_data.details do webhook e peça ao cliente para reenviar o arquivo por outro meio.

Media id expirado. Depois de 7 dias, não há como recuperar a mídia pela API. Por isso o download deve acontecer logo após o webhook.

Como fazer isso com a Vectalk

Na Vectalk, os webhooks são os mesmos da Meta, então o media id chega no mesmo campo. Para a mídia, a API da Vectalk oferece GET https://apis.vectalk.com.br/api/media/{mediaId}, que devolve a URL temporária (válida por cerca de 5 minutos), e GET https://apis.vectalk.com.br/api/media/{mediaId}/download, que já devolve o arquivo binário com o Content-Type correto, eliminando o segundo passo. As duas rotas usam o token de instância. Crie sua conta em apps.vectalk.com.br/register.

Para o caminho inverso, enviar arquivos ao cliente, veja como enviar imagem, PDF, áudio e vídeo pela API. Se o seu bot recebe muitos arquivos, os exemplos completos estão nos tutoriais de bot em Python e bot em Node.js. O guia geral é o da API oficial do WhatsApp.

Perguntas frequentes

Quanto tempo a URL da mídia fica válida?

5 minutos. Depois disso, consulte o media id de novo para gerar uma URL nova. O media id recebido por webhook vale 7 dias.

Posso guardar a URL da Meta no banco e abrir depois?

Não. A URL expira em 5 minutos e exige o token para download. Guarde o arquivo no seu storage e salve no banco o caminho dele.

Preciso do token para baixar a URL?

Sim. A documentação da Meta diz que a requisição falha se você omitir o token. Envie Authorization: Bearer <token> tanto na consulta do media id quanto no download.

Qual o tamanho máximo de um arquivo recebido?

Pela tabela de tipos suportados da Meta, imagens vão até 5 MB, áudio e vídeo até 16 MB e documentos até 100 MB. Figurinhas têm limites menores.

Como saber se o arquivo baixado está íntegro?

Calcule o SHA-256 do conteúdo e compare com o sha256 do webhook ou da resposta do media id. Nos exemplos da Meta, o valor vem em base64.


Fontes oficiais consultadas em 28/10/2026: Media (Meta), Webhook de mensagens de imagem (Meta), Webhook de mensagens de documento (Meta), Códigos de erro (Meta), Mídia na API da Vectalk, PutObjectCommand (AWS SDK for JavaScript v3), upload_fileobj (boto3), LGPD (Lei 13.709/2018).

Teste a API oficial do WhatsApp hoje

Crie sua conta, gere o token e envie a primeira mensagem seguindo o guia da documentação. Mesmos payloads e webhooks da Cloud API da Meta.