Pular para o conteúdo principal

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.

Campos comuns de todo recebido

Todo message.received traz no envelope: type, instance_id (o número que recebeu), sender ({ jid, phone, lid, name }), e no payload: message_id, wa_message_id, from, body, e quoted_id + quoted_participant (autor da citada) quando for resposta. Mensagens de grupo: veja Grupos. Telefones são gravados como o JID de telefone do cliente (conversa unificada entre todos os seus números).

O campo to

Aceita telefone em E.164 (+5511999999999), JID de contato ([email protected]), JID de grupo ([email protected]) e o @lid do contato — o identificador de privacidade que a API devolve em sender.lid e nos participantes de grupo. O @lid é traduzido para o telefone quando o número conectado já conhece aquele LID; quando não conhece, a mensagem sai pelo próprio @lid. Ver Contatos com @lid. No trilho oficial o destino é sempre o telefone.


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 } }
  • url ou base64. ptt: true (áudio) = nota de voz. caption vale 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
} }
Mídia de conversa é PRIVADA

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_phone": "+5511977776666" }
  • contact_phone é o telefone do contato do cartão — a pessoa que você está compartilhando, nunca o destinatário. Com ele montamos o vCard, incluindo o waid que faz o WhatsApp reconhecer o número como contato e oferecer "conversar".
  • Já tem o cartão pronto? Mande contact_vcard e pronto — aí contact_name/contact_phone não são usados para montar nada.
  • contact_name sem contact_phone devolve 400 contact_phone_required.

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 pelo poll_message_id. Voto múltiplo (selectable_count > 1) traz vários itens em selected.

Não existe no trilho oficial

A Cloud API da Meta não tem enquete — é a única lacuna de paridade entre os trilhos, e ela é definitiva. Num projeto oficial, POST /messages/poll devolve 422 official_poll_unsupported e aponta a alternativa, que é uma lista de opções. Ver API oficial.

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" }] }
Comportamento

O WhatsApp restringe botões/listas interativos a contas da API oficial. Como o bZapper envia pelo protocolo multi-dispositivo, o que sai é sempre 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.

No trilho oficial eles são nativos e renderizam: o bZapper usa o interativo da Cloud API sempre que o pedido cabe nos limites da Meta (até 3 botões, título de até 20 caracteres; até 10 linhas de lista, título de até 24). Acima disso, cai no mesmo menu de texto — a mensagem chega, só não chega clicável. A resposta continua chegando como type: "text", agora com o id da opção escolhida em payload.reply_id.

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 (repetir texto idêntico para muitos destinatários 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.
Segurança do OTP

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.

Template aprovado — só no trilho oficial​

Envio — POST /messages/template

{ "to": "+5511999999999", "template": "pedido_confirmado", "language": "pt_BR",
"variables": ["Ana", "10432"], "header_variables": ["10432"] }

É o único jeito de iniciar conversa fora da janela de 24 horas da Cloud API. Você manda o nome do template; o id que a Meta deu a ele nunca aparece.

  • language pode ser omitido quando o nome existe num só idioma. Se existir em mais de um, a resposta é 409 template_ambiguous — mandar no idioma errado é pior que perguntar.
  • A quantidade de variables tem que bater com o template aprovado. Conferimos antes de chamar a Meta: divergência é a causa nº 1 do erro 132000.
  • Em template de categoria AUTHENTICATION, mande code em vez de variables.

Num projeto do trilho não oficial este endpoint devolve 409 template_official_only: lá não existe aprovação prévia, e portanto não há o que enviar por nome — mande o texto direto em /messages/text.

Criar, listar e acompanhar a aprovação: API oficial.


Tabela rápida (tipos recebidos)​

typeCampos-chave no payload
textbody
image/video/audio/document/stickerbody (caption), media.{url,mime_type,filename,size}
locationlatitude, longitude, body (nome)
contactbody (nome)
reactionbody (emoji), quoted_id
pollpoll.{name,options}
poll_votepoll_vote.{poll_message_id,selected[]}, body

otp e template são só de envio. O código do OTP nunca volta no recebimento/inbox, e o que chega de um template enviado é a resposta da pessoa — texto comum, ou type: "text" com reply_id quando ela toca num botão.

Veja também Webhooks (assinatura/entrega) e Atendimento (conversa unificada por cliente).