CRPRO HubEntrar no painel

Como enviar imagem, áudio e documento pela API do WhatsApp

Mandar um arquivo são duas chamadas, não uma: primeiro o arquivo sobe e vira um id, depois o id vai no corpo da mensagem. Este guia cobre os dois passos, a alternativa por URL, e as duas rotas que quase ninguém lembra que existem — baixar e remover a mídia.

O que você precisa

  • Uma chave de API (hub_pk_...) com o escopo messages:send — o mesmo escopo cobre o upload e o envio. Crie em /painel/chaves.
  • O id do canal. Liste os canais da organização em GET /api/v1/channels.
  • Uma janela de atendimento aberta com o contato. Mídia avulsa é mensagem livre: fora das 24 horas, só sai dentro de um template.

Passo 1: subir o arquivo

O corpo desta rota é multipart/form-data, não JSON. O arquivo vai no campo file. Não existe campo JSON com o arquivo em base64 — e nem adiantaria: o payload de uma mensagem enviada é limitado a 256 KiB, então um arquivo embutido no JSON estouraria o limite antes de chegar à Meta.

Upload
curl -X POST https://crprohub.com/api/v1/channels/CHANNEL_UUID/media \
  -H "Authorization: Bearer hub_pk_EXEMPLO_NAO_REAL" \
  -F "file=@foto.jpg"
Resposta
{
  "data": {
    "media_id": "1234567890123456",
    "mime_type": "image/jpeg",
    "size": 82190
  }
}

O media_id é um número de 5 a 30 dígitos, atribuído pela Meta. Ele expira em poucos dias do lado da Meta. Guardar esse id no seu banco como referência permanente do arquivo é o erro clássico aqui: seis meses depois ele não resolve mais nada. Guarde o arquivo do seu lado e suba de novo quando precisar reenviar.

Esta rota não usa Idempotency-Key — o header é exigido só no envio da mensagem. Repetir o upload sobe o arquivo outra vez e devolve outro media_id; nenhuma mensagem é enviada por isso, mas vale evitar o desperdício em retentativa automática.

Passo 2: enviar a mensagem

O envio é o mesmo endpoint de qualquer outra mensagem. Muda o type e o objeto que acompanha ele — image, audio, video, document ou sticker — com o id devolvido no passo 1.

Envio por media ID
curl -X POST https://crprohub.com/api/v1/channels/CHANNEL_UUID/messages \
  -H "Authorization: Bearer hub_pk_EXEMPLO_NAO_REAL" \
  -H "Idempotency-Key: 7c1f0b2e-9a44-4f61-b0d3-8e5c2a1d4f90" \
  -H "Content-Type: application/json" \
  -d '{"to":"5521999999999","type":"image","image":{"id":"1234567890123456"}}'
Resposta
{
  "data": {
    "message_id": "wamid.EXEMPLO123",
    "status": "accepted"
  }
}

A resposta é 202: aceita e enfileirada, não entregue. Vale tudo que está em enviar mensagem — a Idempotency-Key é obrigatória, e o estado final chega pelo evento message.status no webhook. O nome do objeto tem que casar com o type: mandar type: "image" com um objeto document responde 400 INVALID_MESSAGE.

ID ou URL: qual usar

O mesmo objeto aceita uma URL https pública no lugar do id. Aí não tem passo 1 — é uma chamada só.

Envio por URL
curl -X POST https://crprohub.com/api/v1/channels/CHANNEL_UUID/messages \
  -H "Authorization: Bearer hub_pk_EXEMPLO_NAO_REAL" \
  -H "Idempotency-Key: 1d8e5a37-6b02-4c99-9f14-3a7b8c0d2e56" \
  -H "Content-Type: application/json" \
  -d '{"to":"5521999999999","type":"document","document":{"link":"https://exemplo.com/contrato.pdf"}}'

A diferença não é de estilo. Com id, o arquivo já está nos servidores da Meta antes de a mensagem existir: o envio não depende do seu host estar de pé, nem de a URL continuar pública, nem da latência de download no momento do disparo. Com link, a Meta busca o arquivo na hora — e uma URL atrás de autenticação, com redirecionamento, ou que expira, faz a mensagem falhar depois do 202, quando o seu código já respondeu que deu certo.

A exceção é arquivo grande. O upload por POST .../media passa pelo host do CRPRO Hub, que pode ter um limite de tamanho de requisição menor do que o aceito diretamente pela Meta. Para um PDF de dezenas de megabytes, a URL é o caminho confiável.

Tamanhos aceitos

  • Imagem — 5 MiB
  • Sticker (webp) — 500 KiB
  • Áudio e vídeo — 16 MiB
  • Documento (PDF e outros formatos de escritório) — 100 MiB

Esses limites são validados pelo próprio Hub, contra uma lista fechada de tipos aceitos: um arquivo grande demais ou de um tipo fora da lista responde 400 INVALID_MEDIA antes de qualquer chamada à Meta. A tabela completa, com os limites de taxa, está em limites.

Baixar e remover

Quando um cliente manda uma foto, o webhook de mensagem recebida traz o media_id — não o arquivo. Para ter os bytes, use GET /api/v1/channels/{id}/media/{mediaId}. Repare que o escopo aqui é channels:read, não messages:send: uma chave que só lê consegue baixar mídia recebida sem poder enviar nada.

Baixar
curl https://crprohub.com/api/v1/channels/CHANNEL_UUID/media/1234567890123456 \
  -H "Authorization: Bearer hub_pk_EXEMPLO_NAO_REAL" \
  -o arquivo-baixado

Esta é a única rota da API que não devolve JSON. Não há envelope { "data": ... } — vem o conteúdo binário do arquivo, com o Content-Type real da mídia. Passar essa resposta por um .json() genérico do seu cliente HTTP quebra; trate como stream.

O DELETE na mesma rota apaga o arquivo dos servidores da Meta.

Remover
curl -X DELETE https://crprohub.com/api/v1/channels/CHANNEL_UUID/media/1234567890123456 \
  -H "Authorization: Bearer hub_pk_EXEMPLO_NAO_REAL"
Remover não é reversível, e o efeito passa do arquivo: mensagens antigas que referenciam aquele media_id deixam de conseguir baixá-lo. Se a intenção é liberar espaço, não faça nada — o id expira sozinho em poucos dias. O DELETE é para quando o conteúdo não pode continuar existindo lá.

Quando falha

  • 401 — a chave está errada, revogada, ou você mandou o valor sem o prefixo Bearer.
  • 403 — falta escopo. messages:send para subir, enviar e remover; channels:read para baixar.
  • 404 — o canal ou a mídia não existe, ou pertence a outra organização. A API responde 404 nos dois casos de propósito: 403 revelaria que o recurso existe.
  • 409 — Idempotency-Key reaproveitada com corpo diferente. Acontece bastante aqui: o mesmo arquivo subiu duas vezes, gerou dois media_id, e o segundo envio reusou a chave do primeiro.
  • 400 INVALID_MESSAGE — o corpo é válido como JSON mas inválido como mensagem: type sem o objeto correspondente, media_id fora do formato numérico de 5 a 30 dígitos, número malformado.
  • 429 — limite de taxa. O envio é 60 por minuto por canal; a chave inteira tem 120 requisições por minuto, e cada upload consome uma. Veja limites.

Toda resposta traz um header x-request-id. Guarde-o no seu log: é o que liga a sua chamada ao registro em /painel/logs. A tabela completa de códigos está em erros.

O token do canal (hub_ch_...) autentica as três rotas de mídia, como alternativa à chave de API. Ele vale só para o canal que o emitiu e nunca autoriza subir, baixar ou remover mídia de outro canal da organização. O contrato completo dos endpoints está em referência de mídia.