Tipos de mensagem — envio e recebimento
Esta referência detalha o que você envia (request) e o que você recebe
(o payload do webhook message.received e do histórico em
GET /conversations/{jid}/messages) para cada tipo. Use isso para consumir
as informações conforme a sua necessidade.
Todo message.received traz no envelope: type, instance_id (o número que
recebeu), sender ({ jid, lid, name }), e no payload: message_id,
wa_message_id, from, body, e quoted_id quando for resposta. Telefones
são gravados como o JID de telefone do cliente (conversa unificada entre
todos os seus números).
Texto
Envio — POST /messages/text
{ "to": "+5511999999999", "body": "Olá 👋" }
Recebido (type: "text")
{ "type": "text", "body": "Olá 👋", "from": "[email protected]" }
Imagem · Vídeo · Documento · Áudio · Sticker
Envio — POST /messages/{image|video|document|audio|sticker}
{ "to": "+5511999999999",
"media": { "url": "https://.../foto.jpg", "caption": "Olha isso", "filename": "foto.jpg", "mimetype": "image/jpeg", "ptt": false } }
urloubase64.ptt: true(áudio) = nota de voz.captionvale para image/video/document.
Recebido (type: "image" | "video" | "audio" | "document" | "sticker")
{ "type": "image",
"body": "Olha isso", // = caption
"media": {
"id": "uuid",
"url": "https://api.bzapper.com.br/media/uuid?exp=...&sig=...",
"mime_type": "image/jpeg", "filename": "foto.jpg", "size": 84211
} }
A mídia recebida não é pública: vai para um bucket privado
(files.bzapper.com.br, separado dos assets públicos em assets.bzapper.com.br).
A media.url é uma URL pré-assinada SigV4 com TTL (MEDIA_URL_TTL, ~24h,
configurável — ver config.go) — ela expira, então não a armazene.
A referência estável é GET /media/{id} (com exp+sig): a API responde
302 redirecionando para uma URL pré-assinada fresca, e o cliente baixa
direto do Spaces/CDN. Sempre que precisar do arquivo, refaça esse GET (não
guarde a media.url expirada). audio com ptt é nota de voz.
Localização
Envio — POST /messages/location
{ "to": "+5511999999999", "latitude": -23.561, "longitude": -46.656, "name": "Av. Paulista", "address": "São Paulo" }
Recebido (type: "location")
{ "type": "location", "body": "Av. Paulista", "latitude": -23.561, "longitude": -46.656 }
Contato (vCard)
Envio — POST /messages/contact
{ "to": "+5511999999999", "contact_name": "Suporte", "contact_vcard": "BEGIN:VCARD..." }
- Sem
contact_vcard, geramos um vCard simples a partir do nome/telefone.
Recebido (type: "contact") — body = nome exibido.
Reação (emoji)
Envio — POST /messages/reaction
{ "to": "+5511999999999", "quoted_message_id": "<wa_message_id>", "emoji": "❤️" }
Recebido (type: "reaction") — body = emoji; quoted_id = mensagem reagida.
Enquete (poll)
Envio — POST /messages/poll
{ "to": "+5511999999999", "name": "Qual horário?", "options": ["Manhã", "Tarde", "Noite"], "selectable_count": 1 }
Recebido — a enquete (type: "poll")
{ "type": "poll", "body": "Qual horário?",
"poll": { "name": "Qual horário?", "options": ["Manhã", "Tarde", "Noite"] } }
Recebido — o VOTO (type: "poll_vote")
{ "type": "poll_vote",
"body": "Tarde",
"poll_vote": {
"poll_message_id": "<wa_message_id da enquete>",
"selected": ["Tarde"] // opções escolhidas, já resolvidas
} }
O voto chega criptografado pelo WhatsApp (apenas hashes). O bZapper decifra e resolve os hashes contra as opções da enquete original, entregando os nomes em
poll_vote.selected. Correlacione com a enquete pelopoll_message_id. Voto múltiplo (selectable_count > 1) traz vários itens emselected.
Botões e Listas (menu)
Envio — POST /messages/{buttons|list}
{ "to": "+5511999999999", "body": "Confirma o pedido?", "buttons": [{ "id": "sim", "title": "Sim" }, { "id": "nao", "title": "Não" }] }
O WhatsApp restringe botões/listas interativos a contas da API oficial. Para
remetentes não-oficiais, o bZapper envia automaticamente um menu de texto
numerado equivalente (fallback estável que sempre entrega). A resposta do
cliente chega como type: "text" com o texto/numero escolhido — trate como
texto comum.
OTP (código de verificação)
Envio — POST /messages/otp
{ "to": "+5511999999999", "code": "738291", "expiry_minutes": 5 }
O OTP vai em duas mensagens no WhatsApp: um texto de contexto + um balão só com o código (fácil de copiar com long-press, em qualquer aparelho). É 1 OTP lógico e conta como 1 envio para cobrança.
- Se você omitir
body, a API gera o texto no idioma da conta com variações aleatórias (anti-ban — repetir texto idêntico em massa facilita o fingerprint do WhatsApp).expiry_minutes(opcional) só é mencionado no texto. - O código nunca é persistido em claro nem exibido no inbox: guardamos apenas
uma versão mascarada (ex.:
••••91) para auditoria/UX, e o echoguard impede que o código apareça na transcrição.
Trate o code como segredo. O bZapper não o devolve em nenhuma listagem de
conversa nem em webhooks de status — só você (que o gerou) o conhece.
Tabela rápida (tipos recebidos)
type | Campos-chave no payload |
|---|---|
text | body |
image/video/audio/document/sticker | body (caption), media.{url,mime_type,filename,size} |
location | latitude, longitude, body (nome) |
contact | body (nome) |
reaction | body (emoji), quoted_id |
poll | poll.{name,options} |
poll_vote | poll_vote.{poll_message_id,selected[]}, body |
otpé só de envio (POST /messages/otp) — o código nunca volta no recebimento/inbox.
Veja também Webhooks (assinatura/entrega) e Atendimento (conversa unificada por cliente).