7. Mensagens#
Todas as operações assíncronas desta seção (send, forward, delete, edit) são processadas em fila. A API retorna 202 Accepted com o message_id da Catcher (UUID pré-gerado na aceitação da request), idempotency_key e task_id para rastreamento e deduplicação segura de retries do cliente.
O que o canal oficial suporta#
A plataforma oficial da Meta (Cloud API) suporta os tipos abaixo. Envie sempre pelo mesmo endpoint REST — a Catcher roteia para a Cloud API.
| Suportado | Não suportado pela Cloud API |
|---|---|
| Texto, texto com preview de link | Enquete (poll) |
| Imagem, vídeo, áudio (PTT), documento, sticker | View-once, PTV, GIF |
| Reação | Evento (event) |
| Localização | Status / Stories |
| Contato (cartão) | Encaminhar, apagar, editar mensagem |
| Template aprovado (HSM) — ver Templates | Botões interativos "crus" (use um template com botões) |
Um tipo não suportado retorna 501 com uma mensagem clara (… is not supported for the official channel) — não é falha da sua integração, é uma limitação da
Cloud API. Fora da janela de 24h, um envio livre retorna
409 OUTSIDE_SERVICE_WINDOW; use um template aprovado para
reabrir a conversa.
Os toques em botão/lista de um template chegam de volta como o evento
message.interactive_reply_receivedcom oselected_ide o texto selecionado — pronto para automação.
Header obrigatório: Idempotency-Key#
Todos os endpoints assincronos de mensagens exigem o header HTTP Idempotency-Key.
- Use um valor único por operação lógica.
- Repetir a mesma combinação
instanceId+ tipo de operação +Idempotency-Keynão cria uma nova task. - O retry recebe o estado atual do mesmo request (
queued,sentoufailed) com o mesmomessage_ide o mesmotask_id. - Clientes browser podem enviar esse header diretamente: ele faz parte dos headers permitidos no preflight CORS junto com
Authorization,Content-Type,X-API-KeyeX-CSRF-Token.
Formato de destinatário#
O campo to aceita:
- Número de telefone:
554137984905(será convertido para JID automaticamente) - JID individual:
554137984905@s.whatsapp.net
Resposta padrão (202)#
Todas as operações que criam uma nova mensagem retornam:
{
"status": "queued",
"message_id": "550e8400-e29b-41d4-a716-446655440000",
"task_id": "asynq:task:xxxx-xxxx",
"idempotency_key": "msg-2026-03-21-0001"
}
message_id— UUID do BiaZap, pre-gerado no momento da aceitacao. E o mesmo valor que aparece como elemento demessage_idsnos webhooksmessage.sent,message.delivered,message.read,message.played. Use este campo para correlacionar a resposta sincrona com os eventos assincronos (no consumer,message_ids[0]=message_idda resposta 202).idempotency_key— eco do header enviado pelo cliente. Também aparece no webhookmessage.sent(campoidempotency_key) quando a mensagem foi originada via API.task_id— identificador interno do Asynq. Util para debug/NOC; não deve ser usado como chave de correlação primaria (usemessage_idouidempotency_key).
Operações que não criam um novo Message (delete, edit) retornam apenas status, task_id e idempotency_key; o cliente já possui o message_id alvo da operação.
Repetir a mesma chave idempotente devolve o mesmo
message_id,task_ide o estado atual (queued,sentoufailed) sem enfileirar duplicidade. Este e o caminho oficial para um cliente recuperar omessage_idcaso tenha perdido a resposta original ou precise verificar o estado antes do primeiro webhook.
Confirmando entrega de verdade (o 202 não é entrega)#
O 202 significa apenas "aceito na fila". NÃO significa que a mensagem saiu, chegou, ou renderizou no destinatário. Para confirmar de verdade, acompanhe a escada de eventos (webhook/SSE/WS) correlacionando por message_id:
| Evento | Prova | NÃO prova |
|---|---|---|
202 queued (resposta HTTP) |
a task entrou na fila | que saiu / chegou |
message.sent |
o worker despachou pro WhatsApp; a Meta aceitou o proto | que chegou no aparelho |
message.delivered |
chegou no aparelho do destinatário | que renderizou / tocou |
message.read |
o destinatário abriu e o chat renderizou (texto, imagem, documento, sticker, localização, contato, link-preview) | — |
message.played |
o destinatário tocou a mídia (áudio/PTT, vídeo) | — |
O salto crítico é delivered → read/played: mídia pode entregar e falhar em renderizar/tocar no cliente (proto incompleto, container errado). Se um envio chega em delivered mas nunca dá read/played, trate como não consumido — não como entregue. Regra prática: "sem read/played, não chegou de verdade."
Pré-condição importante: o read/played só dispara se o destinatário tiver recibos de leitura ligados E efetivamente abrir/tocar a mensagem. Se o destinatário não produz recibo, o piso de verificação é message.delivered (chegou no aparelho) + inspecionar o message.received do lado dele e conferir que o conteúdo bate (==) com o enviado: mesma caption/content, mesmo message_type, media_id presente, phone correto. Isso prova que chegou correto, mesmo sem o sinal de leitura.
Campo quoted_id (reply)#
Todos os endpoints de envio que listam quoted_id aceitam:
- UUID BiaZap (formato
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx): e o mesmo UUID retornado emmessage_idsnos webhooks (message.received,message.sent, etc). A API resolve internamente para o ID hexadecimal do WhatsApp antes de enfileirar. - ID hexadecimal do WhatsApp (ex.:
3EB0ABC123...): passa direto, sem lookup.
Se o quoted_id for um UUID e não existir no banco (mensagem removida, pertence a outra instância, ou nunca foi recebida por está instância), a API responde 404 Not Found com error_code=MESSAGE_NOT_FOUND. Não ha enfileiramento parcial: a mensagem so e enviada com reply válido ou não e enviada.
Isto vale para
SendText,SendImage,SendVideo,SendAudio,SendDocument,SendSticker,SendLocation,SendContacte para os equivalentes de envio agendado viaPOST /v1/instances/{instanceId}/messages/scheduled.
Modo passivo (409 PASSIVE_MODE_ENABLED)#
Quando a instância está configurada em modo passivo (passive_mode_enabled=true em GET/PATCH /v1/instances/{id}/settings), todos os endpoints de envio (text, image, video, audio, document, sticker, location, contact, reaction, template) são bloqueados antes do enfileiramento:
{
"error_code": "PASSIVE_MODE_ENABLED",
"message": "instance is in passive mode — set passive_mode_enabled=false in /v1/instances/{id}/settings or pass X-Force-Send: true to override (audit-logged)",
"trace_id": "..."
}
- HTTP
409 Conflict. - Bypass: header
X-Force-Send: true(registra audit logaudit_action=passive_mode.force_send). - Modo passivo falha fechado: um erro transitorio no banco do tenant retorna
503/500em vez de liberar o envio (proposito: não vazar outbound de uma instância sob hold legal/monitoramento).
Guard anti-spam (409 BLOCKED_*)#
Endpoints de envio passam por um guard anti-spam antes de enfileirar. Qualquer regra que dispare devolve 409 Conflict:
error_code |
Regra | Campos extras no payload |
|---|---|---|
BLOCKED_DUPLICATE_CONTENT |
Conteúdo identico já enviado nas últimas 24h | remote_jid, content_hash, first_sent_at |
BLOCKED_NO_RECIPROCITY |
Envios consecutivos sem nenhum inbound do contato — limite fixo | remote_jid, count, threshold |
BLOCKED_TEMPERATURE_LIMIT |
max_consecutive da curva de temperatura do contato excedido |
remote_jid, score, tier, max_consecutive, outbound_consecutive |
- A regra de conteúdo duplicado é controlada por
anti_spam_guard_enabled(defaulttrue). - O limite consecutivo sem resposta é controlado pela combinação
anti_spam_guard_enabled+anti_spam_temperature_enabled. Quando ambas estão ativas (default), a curva de temperatura por contato substitui o limite fixo — apenasBLOCKED_TEMPERATURE_LIMITpode disparar. Quando a temperatura está desativada, apenasBLOCKED_NO_RECIPROCITYpode disparar. As duas nunca rodam ao mesmo tempo. delete,edit,reactioneforwardsão isentos da regra de conteúdo duplicado (não ha shape de conteúdo comparavel), mas continuam contando para o limite consecutivo.- Bypass: header
X-Force-Send: true(registra audit logaudit_action=anti_spam.force_send).
POST/v1/instances/{instanceId}/messages/text#
Envia mensagem de texto.
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"text": "Ola! Como posso ajudar?",
"quoted_id": "3EB0ABC123...",
"mentions": ["5541988888888@s.whatsapp.net"]
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | Destinatário (telefone ou JID) |
text |
string | sim | Conteúdo da mensagem |
quoted_id |
string | não | ID da mensagem respondida (UUID BiaZap ou hex WhatsApp; ver nota geral de quoted_id acima) |
mentions |
[]string | não | JIDs de usuários mencionados |
Regras do humanize:
- Controlado por instância via
GET/PATCH /v1/instances/{instanceId}/settings(humanize_enabled, defaulttrue). - Quando ativo, o processor envia
composingimediatamente antes deSendTexte esperanumero_de_caracteres(texto) * multipliersegundos, commultiplieraleatorio entre0.1e0.5. - A aplicação ocorre apenas em mensagens de texto; mensagens de mídia seguem o fluxo legado.
- Se a leitura da configuração falhar, o envio segue imediatamente para não bloquear a fila.
Envio de mídia: media_url, media_id ou arquivo direto#
Os endpoints POST /v1/instances/{instanceId}/messages/{image|video|audio|document|sticker} aceitam as tres formas de origem no mesmo endpoint:
application/jsoncommedia_url: o servidor baixa a URL sincronamente durante o request (~500ms-2s para arquivos pequenos), aplica validação SSRF/MIME/64 MB, armazena no R2/S3, gera ummedia_idinterno e enfileira o envio usando esse ID.application/jsoncommedia_id: reutiliza uma mídia já armazenada pelo endpointPOST /v1/instances/{instanceId}/mediaou por mídia recebida.multipart/form-datacom campofile: envia o arquivo binario direto no próprio endpoint; a API armazena em R2, gera ummedia_ide enfileira o envio usando esse ID.
Arquitetura de mídia: API resolve, worker só envia#
Independente da forma de origem (URL, media_id existente ou multipart), a API sempre garante que os bytes estejam armazenados no R2 antes de retornar 202. O worker só recebe media_id na fila Asynq — nunca uma URL externa para baixar. Isso elimina classe inteira de falha silenciosa em que a CDN externa bloqueia download depois do 202 e a mensagem nunca chega.
Prático para você (consumidor da API):
- Falha de download é sincrona: se a URL retorna 4xx, 5xx, timeout, ou MIME bloqueado, você recebe
502 MEDIA_FETCH_FAILEDno momento da chamada. NÃO vai pra fila, não tem retry silencioso. Retry o request com URL válida ou faca upload prévio. - Custo de latência: requests com
media_urlagora levam ~500ms-2s a mais que commedia_id(tempo do download + upload R2). Para arquivos grandes (>10MB), considere fazer upload prévio viaPOST /v1/instances/{id}/mediae reutilizar omedia_idretornado. - Cache automático: cada
media_urlbaixado vira uma linha no R2 comsource="url-cache"e TTL de 7 dias. Reaproveitamento da mesma URL durante esse período é livre.
Campos textuais do envio (to, caption, file_name, ptt, quoted_id, mentions) podem ir no multipart como campos de formulario. mentions aceita valores repetidos, CSV ou JSON array.
Exemplo com arquivo direto:
curl -X POST "https://api.catcher.one/v1/instances/$INSTANCE/messages/image" \
-H "X-API-Key: $TOKEN" \
-H "Idempotency-Key: req_01HX9Y..." \
-F "to=554137984905" \
-F "caption=Imagem enviada pela API" \
-F "file=@/caminho/foto.png"
Exemplo com URL externa:
curl -X POST "https://api.catcher.one/v1/instances/$INSTANCE/messages/video" \
-H "X-API-Key: $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: req_01HX9Y..." \
-d '{
"to": "554137984905",
"media_url": "https://meucdn.com/video.mp4",
"caption": "Olha esse vídeo"
}'
# → 202 só sai depois que os bytes estão no R2.
# → 502 MEDIA_FETCH_FAILED se o CDN externo recusar.
Exemplo com upload prévio (recomendado para arquivos grandes ou reutilizados):
# 1. Upload uma vez:
MEDIA_ID=$(curl -sf -X POST "https://api.catcher.one/v1/instances/$INSTANCE/media" \
-H "X-API-Key: $TOKEN" \
-H "Idempotency-Key: upload-$(date +%s)" \
-F "file=@/caminho/video-grande.mp4" | jq -r .media_id)
# 2. Reutilize em N envios sem rebaixar:
for PHONE in 554137984905 554196332719 554163475715; do
curl -X POST "https://api.catcher.one/v1/instances/$INSTANCE/messages/video" \
-H "X-API-Key: $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bulk-$PHONE-$(date +%s)" \
-d "{\"to\":\"$PHONE\",\"media_id\":\"$MEDIA_ID\",\"caption\":\"Veja como ficou\"}"
done
Em JSON, envie
media_urlOUmedia_id. Em multipart, enviefile. Um desses tres e obrigatório.
POST/v1/instances/{instanceId}/messages/image#
Envia imagem.
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"media_url": "https://exemplo.com/foto.png",
"caption": "Confira esta imagem!",
"quoted_id": "",
"mentions": []
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | Destinatário |
media_url |
string | sim* | URL pública da imagem |
media_id |
string | sim* | ID de média pre-enviado via upload |
file |
file | sim* | Arquivo direto via multipart/form-data |
caption |
string | não | Legenda da imagem |
quoted_id |
string | não | ID da mensagem respondida (UUID BiaZap ou hex WhatsApp; ver nota geral de quoted_id acima) |
mentions |
[]string | não | JIDs mencionados |
*Enviar
media_url,media_idOUfile(um dos tres e obrigatório).
POST/v1/instances/{instanceId}/messages/video#
Envia video.
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"media_url": "https://exemplo.com/video.mp4",
"caption": "Assista ao video"
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | Destinatário |
media_url |
string | sim* | URL pública do video |
media_id |
string | sim* | ID de média pre-enviado |
file |
file | sim* | Arquivo direto via multipart/form-data |
caption |
string | não | Legenda |
quoted_id |
string | não | ID da mensagem respondida (UUID BiaZap ou hex WhatsApp; ver nota geral de quoted_id acima) |
mentions |
[]string | não | JIDs mencionados |
*Enviar
media_url,media_idOUfile(um dos tres e obrigatório).
Áudio: dois endpoints, dois jeitos#
O envio de áudio tem dois endpoints separados — escolha pelo tipo de mensagem, sem depender de flags:
| Endpoint | Renderiza como | Waveform | Recibo played |
Use quando |
|---|---|---|---|---|
POST .../messages/voice |
mensagem de voz (PTT) | ✅ ondinha | ✅ | quer o áudio "de voz", tipo gravar segurando o microfone |
POST .../messages/audio |
arquivo de áudio | ❌ player comum | ❌ | quer mandar um arquivo de áudio (música, gravação, anexo) |
Ambos aceitam o mesmo corpo (to + media_url ou media_id ou file).
POST/v1/instances/{instanceId}/messages/voice#
Envia uma mensagem de voz (PTT) — renderizada com waveform (ondinha) + ícone de play, e o toque do destinatário gera o recibo message.played. O áudio é sempre convertido para OGG Opus (mono, 16 kHz, 32 kbps) — formato exigido pelo WhatsApp para voz; uma waveform de 64 bytes é gerada automaticamente. É o jeito de mandar "um áudio de voz".
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"media_url": "https://exemplo.com/audio.mp3"
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | Destinatário |
media_url |
string | sim* | URL pública do áudio (qualquer formato — convertido para OGG Opus) |
media_id |
string | sim* | ID de mídia pré-enviado |
file |
file | sim* | Arquivo direto via multipart/form-data |
quoted_id |
string | não | ID da mensagem respondida (UUID Catcher ou hex WhatsApp) |
mentions |
[]string | não | JIDs mencionados |
*Enviar
media_url,media_idOUfile(um dos três é obrigatório).
Ciclo de vida:
sent → delivered → read → played. Oplayedchega quando o destinatário toca o áudio (requer confirmações de leitura habilitadas no aparelho dele).
POST/v1/instances/{instanceId}/messages/audio#
Envia um arquivo de áudio — um player de áudio comum, sem waveform e sem recibo played. Para uma mensagem de voz (ondinha + played), use /messages/voice. Os bytes vão como estão, sem conversão.
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"media_url": "https://exemplo.com/audio.mp3"
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | Destinatário |
media_url |
string | sim* | URL pública do audio (qualquer formato: MP3, OGG, M4A, WAV, etc) |
media_id |
string | sim* | ID de média pre-enviado |
file |
file | sim* | Arquivo direto via multipart/form-data |
ptt |
bool | não | Compatibilidade retroativa. Padrão false (arquivo de áudio). true força uma mensagem de voz (equivalente a /messages/voice). Prefira o endpoint /messages/voice. |
quoted_id |
string | não | ID da mensagem respondida (UUID Catcher ou hex WhatsApp; ver nota geral de quoted_id acima) |
mentions |
[]string | não | JIDs mencionados |
*Enviar
media_url,media_idOUfile(um dos tres e obrigatório).
POST/v1/instances/{instanceId}/messages/document#
Envia documento (PDF, DOCX, etc).
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"media_url": "https://exemplo.com/relatorio.pdf",
"file_name": "relatorio-marco.pdf"
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | Destinatário |
media_url |
string | sim* | URL pública do documento |
media_id |
string | sim* | ID de média pre-enviado |
file |
file | sim* | Arquivo direto via multipart/form-data |
file_name |
string | não | Nome do arquivo exibido no WhatsApp |
quoted_id |
string | não | ID da mensagem respondida (UUID BiaZap ou hex WhatsApp; ver nota geral de quoted_id acima) |
mentions |
[]string | não | JIDs mencionados |
*Enviar
media_url,media_idOUfile(um dos tres e obrigatório).
POST/v1/instances/{instanceId}/messages/sticker#
Envia sticker (figurinha). A imagem deve estar no formato WebP.
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"media_url": "https://exemplo.com/sticker.webp"
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | Destinatário |
media_url |
string | sim* | URL pública do sticker (WebP) |
media_id |
string | sim* | ID de média pre-enviado |
file |
file | sim* | Arquivo direto via multipart/form-data |
quoted_id |
string | não | ID da mensagem respondida (UUID BiaZap ou hex WhatsApp; ver nota geral de quoted_id acima) |
mentions |
[]string | não | JIDs para mencionar |
*Enviar
media_url,media_idOUfile(um dos tres e obrigatório).
POST/v1/instances/{instanceId}/messages/location#
Envia localização.
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"latitude": -25.4284,
"longitude": -49.2733,
"name": "Praca Tiradentes",
"address": "Centro, Curitiba - PR"
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | Destinatário |
latitude |
float | sim | Latitude |
longitude |
float | sim | Longitude |
name |
string | não | Nome do local |
address |
string | não | Endereço |
quoted_id |
string | não | ID da mensagem respondida (UUID BiaZap ou hex WhatsApp; ver nota geral de quoted_id acima) |
mentions |
[]string | não | JIDs para mencionar |
POST/v1/instances/{instanceId}/messages/contact#
Envia cartao de contato (vCard).
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"contact_name": "Suporte BiaZap",
"contact_phone": "5541988887777"
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | Destinatário |
contact_name |
string | sim | Nome do contato no cartao |
contact_phone |
string | sim | Telefone do contato |
quoted_id |
string | não | ID da mensagem respondida (UUID BiaZap ou hex WhatsApp; ver nota geral de quoted_id acima) |
mentions |
[]string | não | JIDs para mencionar |
POST/v1/instances/{instanceId}/messages/reaction#
Envia reacao (emoji) a uma mensagem.
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"message_id": "3EB0ABC123DEF456",
"emoji": "👍"
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | JID do chat onde está a mensagem |
message_id |
string | sim | ID da mensagem no WhatsApp |
emoji |
string | sim | Emoji da reacao (string vazia para remover) |
POST/v1/instances/{instanceId}/messages/template#
Envia mensagem com botoes (template). Requer conta WhatsApp Business.
Auth: Todos autenticados
Request:
{
"to": "554137984905",
"body_text": "Escolha uma opcao abaixo:",
"footer_text": "Powered by BiaZap",
"buttons": [
{"text": "Comprar", "id": "btn_buy"},
{"text": "Cancelar", "id": "btn_cancel"}
]
}
| Campo | Tipo | Obrigatório | Descricao |
|---|---|---|---|
to |
string | sim | Destinatário |
body_text |
string | sim | Texto principal |
footer_text |
string | não | Texto do rodape |
buttons |
[]object | sim | 1 a 3 botoes |
buttons[].text |
string | sim | Texto exibido no botao |
buttons[].id |
string | sim | ID do botao (retornado no callback) |
Nota: Se a instância não for uma conta Business, a mensagem será rejeitada sem retry.
POST/v1/instances/{instanceId}/messages/link-preview#
Envia uma mensagem de texto com card de preview de link controlado (você define título e descrição). Renderiza no destinatário (tipo nativo ExtendedTextMessage).
Auth: Todos autenticados · Header: Idempotency-Key obrigatório
Preview NÃO é o default. Um link num
POST /messages/textcomum vai como texto puro — o app do destinatário PODE gerar um preview básico do lado dele (fetch client-side), mas isso não é garantido nem controlado por você. Para um card com título/descrição garantidos, use este endpoint.
Request:
{
"to": "554137984905",
"text": "Conhece a Catcher? https://catcher.one",
"url": "https://catcher.one",
"title": "Catcher",
"description": "WhatsApp API multi-tenant"
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
to |
string | sim | Destinatário |
text |
string | sim | Corpo da mensagem (deve conter a URL) |
url |
string | sim | URL do preview |
title |
string | não | Título do card |
description |
string | não | Descrição do card |
quoted_id |
string | não | UUID Catcher OU hex WhatsApp da mensagem citada |
Retorna 202 com message_id (UUID Catcher) + task_id + idempotency_key. Emite message.sent (message_type: text).
POST/v1/instances/{instanceId}/official/send-interactive#
Envia uma mensagem interativa nativa da Cloud API: botões de resposta (reply buttons), lista, botão CTA URL, pedido de localização, endereço, ou um Flow. Só disponível no canal oficial (whatsapp_official).
Auth: Todos autenticados · Header: Idempotency-Key obrigatório
Request:
{
"to": "5541999998888",
"kind": "cta_url",
"interactive": {
"type": "cta_url",
"body": { "text": "Confira nosso catálogo completo" },
"action": {
"name": "cta_url",
"parameters": { "display_text": "Ver catálogo", "url": "https://loja.exemplo.com" }
}
}
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
to |
string | sim | Destinatário (telefone ou JID) |
kind |
string | não | Dica do tipo: button, list, cta_url, location_request, address, flow |
interactive |
object | sim | O objeto interactive da Cloud API (type + body + action), montado por você |
Mais exemplos (o objeto interactive segue o schema da Cloud API):
// Reply buttons (até 3) — o tap volta como interactive_reply_received
{ "to": "5541999998888", "kind": "button", "interactive": {
"type": "button",
"body": { "text": "Confirma o agendamento?" },
"action": { "buttons": [
{ "type": "reply", "reply": { "id": "opt_sim", "title": "Sim" } },
{ "type": "reply", "reply": { "id": "opt_nao", "title": "Não" } } ] } } }
// Lista/menu (rows agrupadas em seções)
{ "to": "5541999998888", "kind": "list", "interactive": {
"type": "list",
"header": { "type": "text", "text": "Menu" },
"body": { "text": "Escolha uma opção" },
"action": { "button": "Ver opções", "sections": [
{ "title": "Planos", "rows": [
{ "id": "row_start", "title": "Plano Start", "description": "Para começar" } ] } ] } } }
// Pedido de localização — o toque abre o "compartilhar localização" do WhatsApp
{ "to": "5541999998888", "kind": "location_request", "interactive": {
"type": "location_request_message",
"body": { "text": "Compartilhe sua localização 📍" },
"action": { "name": "send_location" } } }
Retorna 202 com message_id (UUID Catcher) + task_id + idempotency_key.
O que volta quando o contato interage:
| Interação do contato | Evento(s) que você recebe |
|---|---|
| Tap em reply button / item de lista | message.received (type interactive, content = label tocado) + message.interactive_reply_received (reply_type button_reply/list_reply, selected_id, display_text/title/description) |
| Submit de um Flow | message.interactive_reply_received com reply_type: "nfm" + o JSON da resposta do formulário |
Resposta a location_request |
message.received type location com latitude/longitude, location_address (quando o lugar é nomeado), nome em content, e quoted_msg_id = UUID do pedido — correlacione pedido→resposta por ele |
| Clique no botão CTA URL | Nenhum evento (o link abre no navegador do contato — não gera reply) |
Carrossel: um carrossel é enviável pelo endpoint de template (
POST .../official/send-template) usando componentescarouselno template aprovado. Flow: requer um Flow publicado no Flow Builder da sua WABA (ointeractivereferenciaflow_id/flow_cta). Address: disponibilidade restrita por região pela Meta.