a283d858c6
Full rewrite covering all 8 interactive message types: 1. Menu Texto 2. Botões Quick Reply (tested up to 16 buttons) 3. Botões CTA — URL, Copy, Call 4. Lista Dropdown (10 sections × 3 rows = 30 items max) 5. Enquete/Poll 6. Apenas Botões Reply sem CTA 7. Apenas CTAs sem Quick Reply 8. Carrossel com Imagens (up to 10 cards) Each type includes: - Complete curl example with realistic data - All parameters documented with types and limits - Webhook payload structure received on click/select - JavaScript helpers to parse each response type Also includes: - Platform compatibility table (Android / iOS / WA Web) - Universal webhook router for all interactive response types - Complete limits table per message type - Account restrictions (hosted vs non-hosted) Co-Authored-By: Renato Alará
823 lines
26 KiB
Markdown
823 lines
26 KiB
Markdown
# Mensagens Interativas — Guia Completo
|
||
|
||
Guia de referência para envio e consumo de todos os tipos de mensagens interativas suportados pela API.
|
||
|
||
---
|
||
|
||
## Compatibilidade por Plataforma
|
||
|
||
| Tipo | Android | iOS | WA Web |
|
||
|---|---|---|---|
|
||
| Menu Texto | ✅ | ✅ | ✅ |
|
||
| Botões Quick Reply | ✅ | ✅ | ✅ |
|
||
| Botões CTA (URL / Copy / Call) | ✅ | ✅ | ✅ |
|
||
| Lista Dropdown | ✅ | ✅ | ✅ |
|
||
| Enquete/Poll | ✅ | ✅ | ✅ |
|
||
| Carrossel com Imagens | ✅ | ✅ | ✅ |
|
||
|
||
> **Nota:** Todos os tipos exigem conta **WhatsApp Business** ativa e **não-hosted** (registrada via QR/pair code). Contas hosted (Meta Business Cloud API) podem ter restrições de entrega de mensagens interativas impostas pelo servidor WA.
|
||
|
||
---
|
||
|
||
## Índice
|
||
|
||
1. [Menu Texto](#1-menu-texto)
|
||
2. [Botões Quick Reply](#2-botões-quick-reply)
|
||
3. [Botões CTA — URL, Copy, Call](#3-botões-cta--url-copy-call)
|
||
4. [Lista Dropdown](#4-lista-dropdown)
|
||
5. [Enquete / Poll](#5-enquete--poll)
|
||
6. [Apenas Botões Reply sem CTA](#6-apenas-botões-reply-sem-cta)
|
||
7. [Apenas CTAs sem Quick Reply](#7-apenas-ctas-sem-quick-reply)
|
||
8. [Carrossel com Imagens](#8-carrossel-com-imagens)
|
||
9. [Como Consumir Respostas via Webhook](#9-como-consumir-respostas-via-webhook)
|
||
10. [Limites e Restrições](#10-limites-e-restrições)
|
||
|
||
---
|
||
|
||
## 1. Menu Texto
|
||
|
||
Envia uma mensagem de texto simples com uma lista numerada de opções. Funciona em todas as plataformas sem binary nodes.
|
||
|
||
### Curl
|
||
|
||
```bash
|
||
curl -X POST https://sua-api.com/v1/messages/send_menu \
|
||
-H "Content-Type: application/json" \
|
||
-H "x-api-key: SEU_API_KEY" \
|
||
-d '{
|
||
"instance": "minha-instancia",
|
||
"to": "5511900000001",
|
||
"title": "Menu de Opções",
|
||
"text": "Escolha uma opção:",
|
||
"options": ["Opção 1", "Opção 2", "Opção 3"],
|
||
"footer": "Responda com o número"
|
||
}'
|
||
```
|
||
|
||
### Parâmetros
|
||
|
||
| Campo | Tipo | Obrigatório | Descrição |
|
||
|---|---|---|---|
|
||
| `instance` | string | ✅ | Nome da instância conectada |
|
||
| `to` | string | ✅ | Número do destinatário (DDI+DDD+número) |
|
||
| `title` | string | ❌ | Título do menu |
|
||
| `text` | string | ✅ | Texto da mensagem |
|
||
| `options` | string[] | ✅ | Lista de opções (sem limite fixo) |
|
||
| `footer` | string | ❌ | Texto de rodapé |
|
||
|
||
### Resposta da API
|
||
|
||
```json
|
||
{ "ok": true }
|
||
```
|
||
|
||
### Como o destinatário vê
|
||
|
||
O usuário recebe uma mensagem de texto com as opções numeradas e responde digitando o número correspondente. Não há interatividade nativa — a resposta chega como mensagem de texto comum.
|
||
|
||
---
|
||
|
||
## 2. Botões Quick Reply
|
||
|
||
Botões de resposta rápida que o usuário toca para selecionar. Testado com até **16 botões**. Renderiza nativamente em Android, iOS e WA Web.
|
||
|
||
### Curl
|
||
|
||
```bash
|
||
curl -X POST https://sua-api.com/v1/messages/send_buttons_helpers \
|
||
-H "Content-Type: application/json" \
|
||
-H "x-api-key: SEU_API_KEY" \
|
||
-d '{
|
||
"instance": "minha-instancia",
|
||
"to": "5511900000001",
|
||
"text": "👋 Olá! Como posso ajudar?",
|
||
"footer": "Atendimento 24h",
|
||
"buttons": [
|
||
{"id": "vendas", "text": "🛒 Fazer Pedido"},
|
||
{"id": "suporte", "text": "🔧 Suporte Técnico"},
|
||
{"id": "financeiro", "text": "💰 Financeiro"},
|
||
{"id": "comercial", "text": "📊 Comercial"},
|
||
{"id": "contabil", "text": "📋 Contábil"},
|
||
{"id": "rh", "text": "👥 Recursos Humanos"},
|
||
{"id": "secretaria", "text": "📞 Secretaria"},
|
||
{"id": "diplomas", "text": "🎓 Diplomas"},
|
||
{"id": "diretoria", "text": "🏛️ Diretoria"},
|
||
{"id": "compliance", "text": "✅ Compliance"},
|
||
{"id": "juridico", "text": "⚖️ Jurídico"},
|
||
{"id": "social", "text": "🤝 Ass. Social"},
|
||
{"id": "contratos", "text": "📄 Contratos"},
|
||
{"id": "ti", "text": "💻 Tecnologia da Informação"},
|
||
{"id": "assessoria", "text": "📣 Assessoria"},
|
||
{"id": "voip", "text": "📡 VOIP"}
|
||
]
|
||
}'
|
||
```
|
||
|
||
### Parâmetros
|
||
|
||
| Campo | Tipo | Obrigatório | Descrição |
|
||
|---|---|---|---|
|
||
| `instance` | string | ✅ | Nome da instância |
|
||
| `to` | string | ✅ | Número do destinatário |
|
||
| `text` | string | ✅ | Texto principal |
|
||
| `footer` | string | ❌ | Rodapé |
|
||
| `buttons` | array | ✅ | Lista de botões (ver abaixo) |
|
||
| `buttons[].id` | string | ✅ | ID do botão (retornado no webhook) |
|
||
| `buttons[].text` | string | ✅ | Texto exibido no botão |
|
||
|
||
### Webhook recebido ao clicar
|
||
|
||
```json
|
||
{
|
||
"event": "messages.upsert",
|
||
"data": {
|
||
"messages": [{
|
||
"key": { "remoteJid": "5511900000001@s.whatsapp.net", "fromMe": false },
|
||
"message": {
|
||
"interactiveResponseMessage": {
|
||
"body": { "text": "🛒 Fazer Pedido" },
|
||
"nativeFlowResponseMessage": {
|
||
"name": "quick_reply",
|
||
"paramsJson": "{\"id\":\"vendas\"}"
|
||
}
|
||
}
|
||
}
|
||
}]
|
||
}
|
||
}
|
||
```
|
||
|
||
### Como ler a resposta
|
||
|
||
```javascript
|
||
function parseButtonReply(msg) {
|
||
const interactive = msg.message?.interactiveResponseMessage
|
||
if (!interactive) return null
|
||
|
||
const params = JSON.parse(
|
||
interactive.nativeFlowResponseMessage?.paramsJson || '{}'
|
||
)
|
||
return {
|
||
buttonId: params.id,
|
||
buttonText: interactive.body?.text
|
||
}
|
||
}
|
||
|
||
// Exemplo:
|
||
// { buttonId: 'vendas', buttonText: '🛒 Fazer Pedido' }
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Botões CTA — URL, Copy, Call
|
||
|
||
Botões de ação especiais: abrir URL, copiar código (PIX, cupom, etc.) e ligar. Podem ser combinados na mesma mensagem.
|
||
|
||
### Curl — Combinação URL + Copy + Call
|
||
|
||
```bash
|
||
curl -X POST https://sua-api.com/v1/messages/send_interactive_helpers \
|
||
-H "Content-Type: application/json" \
|
||
-H "x-api-key: SEU_API_KEY" \
|
||
-d '{
|
||
"instance": "minha-instancia",
|
||
"to": "5511900000001",
|
||
"text": "💳 Pagamento via PIX\nValor: R$ 150,00\nPedido: #12345",
|
||
"footer": "Obrigado pela preferência!",
|
||
"buttons": [
|
||
{
|
||
"type": "call",
|
||
"text": "📞 Ligar Suporte",
|
||
"phoneNumber": "+5511900000002"
|
||
},
|
||
{
|
||
"type": "copy",
|
||
"text": "📋 Copiar Chave PIX",
|
||
"copyCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-426614174000"
|
||
},
|
||
{
|
||
"type": "url",
|
||
"text": "🔗 Ver Pedido",
|
||
"url": "https://sualoja.com.br/pedido/12345"
|
||
}
|
||
]
|
||
}'
|
||
```
|
||
|
||
### Parâmetros de cada tipo de botão
|
||
|
||
**Tipo `url`:**
|
||
| Campo | Tipo | Descrição |
|
||
|---|---|---|
|
||
| `type` | `"url"` | Tipo do botão |
|
||
| `text` | string | Texto do botão |
|
||
| `url` | string | URL a abrir (http/https) |
|
||
|
||
**Tipo `copy`:**
|
||
| Campo | Tipo | Descrição |
|
||
|---|---|---|
|
||
| `type` | `"copy"` | Tipo do botão |
|
||
| `text` | string | Texto do botão |
|
||
| `copyCode` | string | Texto copiado ao clicar |
|
||
|
||
**Tipo `call`:**
|
||
| Campo | Tipo | Descrição |
|
||
|---|---|---|
|
||
| `type` | `"call"` | Tipo do botão |
|
||
| `text` | string | Texto do botão |
|
||
| `phoneNumber` | string | Número a ligar (com +DDI) |
|
||
|
||
### Webhook recebido ao clicar em URL
|
||
|
||
```json
|
||
{
|
||
"message": {
|
||
"interactiveResponseMessage": {
|
||
"body": { "text": "🔗 Ver Pedido" },
|
||
"nativeFlowResponseMessage": {
|
||
"name": "cta_open_url",
|
||
"paramsJson": "{\"url\":\"https://sualoja.com.br/pedido/12345\",\"from_notification\":false}"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Webhook recebido ao clicar em Copy
|
||
|
||
```json
|
||
{
|
||
"message": {
|
||
"interactiveResponseMessage": {
|
||
"nativeFlowResponseMessage": {
|
||
"name": "cta_copy",
|
||
"paramsJson": "{\"copy_code\":\"00020126580014br.gov.bcb.pix...\"}"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Como ler a resposta CTA
|
||
|
||
```javascript
|
||
function parseCTAReply(msg) {
|
||
const flow = msg.message?.interactiveResponseMessage?.nativeFlowResponseMessage
|
||
if (!flow) return null
|
||
|
||
const params = JSON.parse(flow.paramsJson || '{}')
|
||
switch (flow.name) {
|
||
case 'cta_open_url': return { action: 'url', url: params.url }
|
||
case 'cta_copy': return { action: 'copy', code: params.copy_code }
|
||
case 'cta_call': return { action: 'call', phone: params.phone_number }
|
||
default: return { action: flow.name, params }
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Lista Dropdown
|
||
|
||
Menu suspenso com seções e itens selecionáveis. Suporta até **10 seções × 3 linhas = 30 itens** no total.
|
||
|
||
### Curl — Cardápio completo (10 seções, 30 itens)
|
||
|
||
```bash
|
||
curl -X POST https://sua-api.com/v1/messages/send_list_helpers \
|
||
-H "Content-Type: application/json" \
|
||
-H "x-api-key: SEU_API_KEY" \
|
||
-d '{
|
||
"instance": "minha-instancia",
|
||
"to": "5511900000001",
|
||
"text": "Cardápio completo do restaurante.\nEscolha uma categoria abaixo:",
|
||
"footer": "Delivery grátis acima de R$ 50",
|
||
"buttonText": "Ver Cardápio",
|
||
"sections": [
|
||
{
|
||
"title": "Hambúrgueres",
|
||
"rows": [
|
||
{"id": "burger_1", "title": "Clássico", "description": "Pão, carne 180g, queijo, alface, tomate - R$ 28,90"},
|
||
{"id": "burger_2", "title": "Bacon Lovers", "description": "Pão, carne 180g, bacon crocante, cheddar - R$ 34,90"},
|
||
{"id": "burger_3", "title": "Veggie Burger", "description": "Pão, hambúrguer de grão de bico, rúcula - R$ 32,90"}
|
||
]
|
||
},
|
||
{
|
||
"title": "Pizzas",
|
||
"rows": [
|
||
{"id": "pizza_1", "title": "Margherita", "description": "Molho de tomate, mussarela, manjericão - R$ 49,90"},
|
||
{"id": "pizza_2", "title": "Pepperoni", "description": "Molho, mussarela, pepperoni fatiado - R$ 54,90"},
|
||
{"id": "pizza_3", "title": "Quatro Queijos","description": "Mussarela, gorgonzola, parmesão, brie - R$ 52,90"}
|
||
]
|
||
},
|
||
{
|
||
"title": "Massas",
|
||
"rows": [
|
||
{"id": "massa_1", "title": "Espaguete Bolonhesa", "description": "Massa al dente com molho bolonhesa caseiro - R$ 38,90"},
|
||
{"id": "massa_2", "title": "Fettuccine Alfredo", "description": "Fettuccine com molho branco cremoso - R$ 42,90"},
|
||
{"id": "massa_3", "title": "Lasanha Especial", "description": "Camadas de massa, carne, presunto, queijo - R$ 45,90"}
|
||
]
|
||
},
|
||
{
|
||
"title": "Saladas",
|
||
"rows": [
|
||
{"id": "salada_1", "title": "Caesar", "description": "Alface romana, croutons, parmesão, molho - R$ 32,90"},
|
||
{"id": "salada_2", "title": "Tropical", "description": "Mix de folhas, manga, palmito, molho - R$ 29,90"},
|
||
{"id": "salada_3", "title": "Caprese", "description": "Tomate, mussarela búfala, manjericão - R$ 34,90"}
|
||
]
|
||
},
|
||
{
|
||
"title": "Frutos do Mar",
|
||
"rows": [
|
||
{"id": "mar_1", "title": "Camarão Grelhado", "description": "Camarões grelhados com manteiga e alho - R$ 62,90"},
|
||
{"id": "mar_2", "title": "Filé de Salmão", "description": "Salmão grelhado com legumes no vapor - R$ 58,90"},
|
||
{"id": "mar_3", "title": "Moqueca de Peixe", "description": "Peixe, leite de coco, dendê, pimentão - R$ 55,90"}
|
||
]
|
||
},
|
||
{
|
||
"title": "Sobremesas",
|
||
"rows": [
|
||
{"id": "doce_1", "title": "Petit Gâteau", "description": "Bolo de chocolate com sorvete de creme - R$ 28,90"},
|
||
{"id": "doce_2", "title": "Pudim", "description": "Pudim de leite condensado tradicional - R$ 18,90"},
|
||
{"id": "doce_3", "title": "Açaí 500ml", "description": "Açaí com granola, banana e leite ninho - R$ 24,90"}
|
||
]
|
||
},
|
||
{
|
||
"title": "Bebidas",
|
||
"rows": [
|
||
{"id": "bebida_1", "title": "Refrigerante 350ml", "description": "Coca-Cola, Guaraná, Sprite, Fanta - R$ 6,90"},
|
||
{"id": "bebida_2", "title": "Suco Natural 500ml", "description": "Laranja, limão, maracujá, abacaxi - R$ 12,90"},
|
||
{"id": "bebida_3", "title": "Água Mineral", "description": "Com ou sem gás 500ml - R$ 4,90"}
|
||
]
|
||
},
|
||
{
|
||
"title": "Cervejas",
|
||
"rows": [
|
||
{"id": "cerveja_1", "title": "Pilsen 600ml", "description": "Brahma, Skol, Antarctica - R$ 12,90"},
|
||
{"id": "cerveja_2", "title": "IPA 473ml", "description": "Colorado, Lagunitas, Goose Island - R$ 18,90"},
|
||
{"id": "cerveja_3", "title": "Weiss 500ml", "description": "Erdinger, Paulaner, Blue Moon - R$ 22,90"}
|
||
]
|
||
},
|
||
{
|
||
"title": "Vinhos",
|
||
"rows": [
|
||
{"id": "vinho_1", "title": "Tinto Seco", "description": "Cabernet Sauvignon, taça 150ml - R$ 25,90"},
|
||
{"id": "vinho_2", "title": "Branco Suave", "description": "Chardonnay, taça 150ml - R$ 23,90"},
|
||
{"id": "vinho_3", "title": "Rosé", "description": "Rosé Provence, taça 150ml - R$ 27,90"}
|
||
]
|
||
},
|
||
{
|
||
"title": "Combos",
|
||
"rows": [
|
||
{"id": "combo_1", "title": "Combo Single", "description": "1 hambúrguer + batata + refri - R$ 39,90"},
|
||
{"id": "combo_2", "title": "Combo Casal", "description": "2 hambúrgueres + batata grande + 2 refri - R$ 69,90"},
|
||
{"id": "combo_3", "title": "Combo Família", "description": "4 hambúrgueres + 2 batatas + jarra - R$ 119,90"}
|
||
]
|
||
}
|
||
]
|
||
}'
|
||
```
|
||
|
||
### Parâmetros
|
||
|
||
| Campo | Tipo | Obrigatório | Descrição |
|
||
|---|---|---|---|
|
||
| `text` | string | ✅ | Texto da mensagem |
|
||
| `footer` | string | ❌ | Rodapé |
|
||
| `buttonText` | string | ✅ | Texto do botão que abre a lista (max 20 chars) |
|
||
| `sections` | array | ✅ | Seções da lista (max 10) |
|
||
| `sections[].title` | string | ✅ | Título da seção (max 24 chars) |
|
||
| `sections[].rows` | array | ✅ | Itens da seção (max 3 por seção) |
|
||
| `rows[].id` | string | ✅ | ID do item (retornado no webhook) |
|
||
| `rows[].title` | string | ✅ | Título do item (max 24 chars) |
|
||
| `rows[].description` | string | ❌ | Descrição do item (max 72 chars) |
|
||
|
||
### Webhook recebido ao selecionar item
|
||
|
||
```json
|
||
{
|
||
"message": {
|
||
"interactiveResponseMessage": {
|
||
"body": { "text": "Clássico" },
|
||
"nativeFlowResponseMessage": {
|
||
"name": "single_select",
|
||
"paramsJson": "{\"id\":\"burger_1\",\"title\":\"Clássico\"}"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Como ler a resposta
|
||
|
||
```javascript
|
||
function parseListReply(msg) {
|
||
const flow = msg.message?.interactiveResponseMessage?.nativeFlowResponseMessage
|
||
if (flow?.name !== 'single_select') return null
|
||
|
||
const params = JSON.parse(flow.paramsJson || '{}')
|
||
return {
|
||
id: params.id,
|
||
title: params.title
|
||
}
|
||
}
|
||
|
||
// Exemplo: { id: 'burger_1', title: 'Clássico' }
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Enquete / Poll
|
||
|
||
Enquete nativa do WhatsApp. Funciona em todas as plataformas sem restrições. O usuário seleciona uma ou mais opções e o resultado é atualizado em tempo real.
|
||
|
||
### Curl
|
||
|
||
```bash
|
||
curl -X POST https://sua-api.com/v1/messages/send_poll \
|
||
-H "Content-Type: application/json" \
|
||
-H "x-api-key: SEU_API_KEY" \
|
||
-d '{
|
||
"instance": "minha-instancia",
|
||
"to": "5511900000001",
|
||
"name": "Qual sua linguagem favorita?",
|
||
"options": ["JavaScript", "Python", "TypeScript", "Go"],
|
||
"selectableCount": 1
|
||
}'
|
||
```
|
||
|
||
### Parâmetros
|
||
|
||
| Campo | Tipo | Obrigatório | Descrição |
|
||
|---|---|---|---|
|
||
| `name` | string | ✅ | Pergunta da enquete |
|
||
| `options` | string[] | ✅ | Opções (min 2, max 12) |
|
||
| `selectableCount` | number | ✅ | Quantas opções o usuário pode marcar (0 = ilimitado) |
|
||
|
||
### Webhook recebido ao votar
|
||
|
||
```json
|
||
{
|
||
"event": "messages.upsert",
|
||
"data": {
|
||
"messages": [{
|
||
"key": { "remoteJid": "5511900000001@s.whatsapp.net" },
|
||
"message": {
|
||
"pollUpdateMessage": {
|
||
"pollCreationMessageKey": {
|
||
"id": "ID_DA_ENQUETE_ORIGINAL"
|
||
},
|
||
"vote": {
|
||
"selectedOptions": ["TypeScript"]
|
||
}
|
||
}
|
||
}
|
||
}]
|
||
}
|
||
}
|
||
```
|
||
|
||
### Como ler a resposta
|
||
|
||
```javascript
|
||
function parsePollVote(msg) {
|
||
const poll = msg.message?.pollUpdateMessage
|
||
if (!poll) return null
|
||
|
||
return {
|
||
pollId: poll.pollCreationMessageKey?.id,
|
||
selectedOptions: poll.vote?.selectedOptions || []
|
||
}
|
||
}
|
||
|
||
// Exemplo: { pollId: '3EB0...', selectedOptions: ['TypeScript'] }
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Apenas Botões Reply sem CTA
|
||
|
||
Botões simples de confirmação/seleção sem nenhum botão de ação externa.
|
||
|
||
### Curl
|
||
|
||
```bash
|
||
curl -X POST https://sua-api.com/v1/messages/send_buttons_helpers \
|
||
-H "Content-Type: application/json" \
|
||
-H "x-api-key: SEU_API_KEY" \
|
||
-d '{
|
||
"instance": "minha-instancia",
|
||
"to": "5511900000001",
|
||
"text": "Confirma o pedido #12345?",
|
||
"footer": "Pedido no valor de R$ 150,00",
|
||
"buttons": [
|
||
{"id": "confirmar", "text": "✅ Confirmar"},
|
||
{"id": "cancelar", "text": "❌ Cancelar"},
|
||
{"id": "adiar", "text": "⏰ Adiar"}
|
||
]
|
||
}'
|
||
```
|
||
|
||
O webhook e a leitura da resposta seguem o mesmo padrão da [Seção 2](#2-botões-quick-reply).
|
||
|
||
---
|
||
|
||
## 7. Apenas CTAs sem Quick Reply
|
||
|
||
Mensagem com somente botões de ação (URL / Call) sem botões de resposta rápida.
|
||
|
||
### Curl
|
||
|
||
```bash
|
||
curl -X POST https://sua-api.com/v1/messages/send_interactive_helpers \
|
||
-H "Content-Type: application/json" \
|
||
-H "x-api-key: SEU_API_KEY" \
|
||
-d '{
|
||
"instance": "minha-instancia",
|
||
"to": "5511900000001",
|
||
"text": "🏪 Loja Virtual\nConfira nossos canais de atendimento:",
|
||
"footer": "Atendimento 24h",
|
||
"buttons": [
|
||
{"type": "url", "text": "🌐 Site Oficial", "url": "https://www.sualoja.com.br"},
|
||
{"type": "url", "text": "📸 Instagram", "url": "https://instagram.com/sualoja"},
|
||
{"type": "call", "text": "📞 WhatsApp Vendas", "phoneNumber": "+5511900000002"}
|
||
]
|
||
}'
|
||
```
|
||
|
||
O webhook e a leitura da resposta seguem o mesmo padrão da [Seção 3](#3-botões-cta--url-copy-call).
|
||
|
||
---
|
||
|
||
## 8. Carrossel com Imagens
|
||
|
||
Cards deslizáveis com imagem, texto e botões. Suporta até **10 cards**, cada um com até **2 botões**. Renderiza nativamente em Android, iOS e WA Web.
|
||
|
||
### Curl — 10 cards (limite máximo)
|
||
|
||
```bash
|
||
curl -X POST https://sua-api.com/v1/messages/send_carousel_helpers \
|
||
-H "Content-Type: application/json" \
|
||
-H "x-api-key: SEU_API_KEY" \
|
||
-d '{
|
||
"instance": "minha-instancia",
|
||
"to": "5511900000001",
|
||
"text": "🛍️ Ofertas Especiais",
|
||
"footer": "Loja Virtual — Entrega Grátis",
|
||
"cards": [
|
||
{
|
||
"body": "iPhone 15 Pro Max 256GB\nR$ 8.999,00 à vista\n12x R$ 833,25",
|
||
"footer": "Frete Grátis",
|
||
"imageUrl": "https://images.unsplash.com/photo-1592750475338-74b7b21085ab?w=400",
|
||
"buttons": [{"id": "buy_1", "text": "Comprar"}, {"id": "info_1", "text": "Detalhes"}]
|
||
},
|
||
{
|
||
"body": "MacBook Air M3 8GB 256GB\nR$ 12.499,00 à vista\n12x R$ 1.166,58",
|
||
"footer": "Garantia 1 ano",
|
||
"imageUrl": "https://images.unsplash.com/photo-1517336714731-489689fd1ca8?w=400",
|
||
"buttons": [{"id": "buy_2", "text": "Comprar"}, {"id": "info_2", "text": "Detalhes"}]
|
||
},
|
||
{
|
||
"body": "Apple Watch Series 9 45mm\nR$ 5.299,00 à vista\n12x R$ 491,58",
|
||
"footer": "Pronta Entrega",
|
||
"imageUrl": "https://images.unsplash.com/photo-1546868871-7041f2a55e12?w=400",
|
||
"buttons": [{"id": "buy_3", "text": "Comprar"}, {"id": "info_3", "text": "Detalhes"}]
|
||
},
|
||
{
|
||
"body": "AirPods Pro 2ª Geração\nR$ 2.499,00 à vista\n12x R$ 233,25",
|
||
"footer": "Cancelamento de Ruído",
|
||
"imageUrl": "https://images.unsplash.com/photo-1600294037681-c80b4cb5b434?w=400",
|
||
"buttons": [{"id": "buy_4", "text": "Comprar"}, {"id": "info_4", "text": "Detalhes"}]
|
||
},
|
||
{
|
||
"body": "iPad Pro M2 11pol 128GB\nR$ 9.999,00 à vista\n12x R$ 916,58",
|
||
"footer": "Chip M2",
|
||
"imageUrl": "https://images.unsplash.com/photo-1544244015-0df4b3ffc6b0?w=400",
|
||
"buttons": [{"id": "buy_5", "text": "Comprar"}, {"id": "info_5", "text": "Detalhes"}]
|
||
},
|
||
{
|
||
"body": "Samsung Galaxy S24 Ultra\nR$ 7.499,00 à vista\n12x R$ 691,58",
|
||
"footer": "Câmera 200MP",
|
||
"imageUrl": "https://images.unsplash.com/photo-1610945265064-0e34e5519bbf?w=400",
|
||
"buttons": [{"id": "buy_6", "text": "Comprar"}, {"id": "info_6", "text": "Detalhes"}]
|
||
},
|
||
{
|
||
"body": "Sony WH-1000XM5\nR$ 2.299,00 à vista\n12x R$ 208,25",
|
||
"footer": "Melhor ANC do mercado",
|
||
"imageUrl": "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400",
|
||
"buttons": [{"id": "buy_7", "text": "Comprar"}, {"id": "info_7", "text": "Detalhes"}]
|
||
},
|
||
{
|
||
"body": "Nintendo Switch OLED\nR$ 2.699,00 à vista\n12x R$ 249,92",
|
||
"footer": "Com Joy-Con",
|
||
"imageUrl": "https://images.unsplash.com/photo-1578303512597-81e6cc155b3e?w=400",
|
||
"buttons": [{"id": "buy_8", "text": "Comprar"}, {"id": "info_8", "text": "Detalhes"}]
|
||
},
|
||
{
|
||
"body": "PlayStation 5 Slim\nR$ 4.499,00 à vista\n12x R$ 416,58",
|
||
"footer": "1TB SSD",
|
||
"imageUrl": "https://images.unsplash.com/photo-1606144042614-b2417e99c4e3?w=400",
|
||
"buttons": [{"id": "buy_9", "text": "Comprar"}, {"id": "info_9", "text": "Detalhes"}]
|
||
},
|
||
{
|
||
"body": "DJI Mini 4 Pro Drone\nR$ 6.999,00 à vista\n12x R$ 641,58",
|
||
"footer": "4K 60fps",
|
||
"imageUrl": "https://images.unsplash.com/photo-1473968512647-3e447244af8f?w=400",
|
||
"buttons": [{"id": "buy_10", "text": "Comprar"}, {"id": "info_10", "text": "Detalhes"}]
|
||
}
|
||
]
|
||
}'
|
||
```
|
||
|
||
### Parâmetros
|
||
|
||
| Campo | Tipo | Obrigatório | Descrição |
|
||
|---|---|---|---|
|
||
| `text` | string | ✅ | Texto acima do carrossel |
|
||
| `footer` | string | ❌ | Rodapé |
|
||
| `cards` | array | ✅ | Cards (min 2, max 10) |
|
||
| `cards[].body` | string | ✅ | Texto do card |
|
||
| `cards[].footer` | string | ❌ | Rodapé do card |
|
||
| `cards[].imageUrl` | string | ✅ | URL da imagem do card |
|
||
| `cards[].buttons` | array | ✅ | Botões do card (max 2) |
|
||
| `buttons[].id` | string | ✅ | ID do botão |
|
||
| `buttons[].text` | string | ✅ | Texto do botão |
|
||
|
||
### Webhook recebido ao clicar em botão do carrossel
|
||
|
||
```json
|
||
{
|
||
"message": {
|
||
"interactiveResponseMessage": {
|
||
"body": { "text": "Comprar" },
|
||
"nativeFlowResponseMessage": {
|
||
"name": "quick_reply",
|
||
"paramsJson": "{\"id\":\"buy_3\"}"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Como ler a resposta
|
||
|
||
```javascript
|
||
function parseCarouselReply(msg) {
|
||
const flow = msg.message?.interactiveResponseMessage?.nativeFlowResponseMessage
|
||
if (flow?.name !== 'quick_reply') return null
|
||
|
||
const params = JSON.parse(flow.paramsJson || '{}')
|
||
return {
|
||
buttonId: params.id,
|
||
buttonText: msg.message.interactiveResponseMessage.body?.text
|
||
}
|
||
}
|
||
|
||
// Exemplo: { buttonId: 'buy_3', buttonText: 'Comprar' }
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Como Consumir Respostas via Webhook
|
||
|
||
### 9.1 Estrutura geral do evento
|
||
|
||
```json
|
||
{
|
||
"event": "messages.upsert",
|
||
"instance": "minha-instancia",
|
||
"data": {
|
||
"messages": [{ ...mensagem... }],
|
||
"type": "notify"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 9.2 Roteador universal de respostas interativas
|
||
|
||
```javascript
|
||
function parseInteractiveReply(msg) {
|
||
const interactive = msg.message?.interactiveResponseMessage
|
||
if (!interactive) return null
|
||
|
||
const flow = interactive.nativeFlowResponseMessage
|
||
const body = interactive.body?.text
|
||
const params = JSON.parse(flow?.paramsJson || '{}')
|
||
|
||
switch (flow?.name) {
|
||
// Botão quick_reply ou carrossel
|
||
case 'quick_reply':
|
||
return { type: 'button', id: params.id, text: body }
|
||
|
||
// Lista dropdown
|
||
case 'single_select':
|
||
return { type: 'list', id: params.id, title: params.title }
|
||
|
||
// CTA — abrir URL
|
||
case 'cta_open_url':
|
||
return { type: 'url', url: params.url }
|
||
|
||
// CTA — copiar código
|
||
case 'cta_copy':
|
||
return { type: 'copy', code: params.copy_code }
|
||
|
||
// CTA — ligar
|
||
case 'cta_call':
|
||
return { type: 'call', phone: params.phone_number }
|
||
|
||
default:
|
||
return { type: 'unknown', name: flow?.name, params }
|
||
}
|
||
}
|
||
```
|
||
|
||
### 9.3 Enquete — roteador
|
||
|
||
```javascript
|
||
function parsePollUpdate(msg) {
|
||
const poll = msg.message?.pollUpdateMessage
|
||
if (!poll) return null
|
||
|
||
return {
|
||
type: 'poll_vote',
|
||
pollId: poll.pollCreationMessageKey?.id,
|
||
selectedOptions: poll.vote?.selectedOptions || []
|
||
}
|
||
}
|
||
```
|
||
|
||
### 9.4 Handler completo de webhook
|
||
|
||
```javascript
|
||
app.post('/webhook', (req, res) => {
|
||
const { event, instance, data } = req.body
|
||
|
||
if (event !== 'messages.upsert') return res.sendStatus(200)
|
||
|
||
for (const msg of data.messages) {
|
||
const from = msg.key.remoteJid.replace('@s.whatsapp.net', '')
|
||
const fromMe = msg.key.fromMe
|
||
|
||
if (fromMe) continue // ignora mensagens enviadas pela própria instância
|
||
|
||
// Verificar tipo de resposta interativa
|
||
const reply = parseInteractiveReply(msg) || parsePollUpdate(msg)
|
||
|
||
if (reply) {
|
||
console.log(`[${instance}] Resposta de ${from}:`, reply)
|
||
handleInteractiveReply(instance, from, reply)
|
||
}
|
||
}
|
||
|
||
res.sendStatus(200)
|
||
})
|
||
|
||
function handleInteractiveReply(instance, from, reply) {
|
||
switch (reply.type) {
|
||
case 'button':
|
||
console.log(`Botão clicado: ${reply.id} — "${reply.text}"`)
|
||
break
|
||
case 'list':
|
||
console.log(`Item selecionado: ${reply.id} — "${reply.title}"`)
|
||
break
|
||
case 'url':
|
||
console.log(`URL aberta: ${reply.url}`)
|
||
break
|
||
case 'copy':
|
||
console.log(`Código copiado: ${reply.code}`)
|
||
break
|
||
case 'poll_vote':
|
||
console.log(`Voto em enquete ${reply.pollId}:`, reply.selectedOptions)
|
||
break
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Limites e Restrições
|
||
|
||
### Limites por tipo
|
||
|
||
| Tipo | Limite |
|
||
|---|---|
|
||
| Botões Quick Reply | Testado com até 16 botões |
|
||
| Lista — seções | Máximo 10 seções |
|
||
| Lista — itens por seção | Máximo 3 itens |
|
||
| Lista — total de itens | Máximo 30 itens |
|
||
| Lista — título de seção | Máximo 24 caracteres |
|
||
| Lista — título de item | Máximo 24 caracteres |
|
||
| Lista — descrição de item | Máximo 72 caracteres |
|
||
| Lista — texto do botão | Máximo 20 caracteres |
|
||
| Carrossel — cards | Mínimo 2, máximo 10 |
|
||
| Carrossel — botões por card | Máximo 2 botões |
|
||
| Poll — opções | Mínimo 2, máximo 12 |
|
||
|
||
### Restrições de conta
|
||
|
||
| Restrição | Detalhe |
|
||
|---|---|
|
||
| Conta hosted (Meta Business Cloud API) | Mensagens interativas podem ser bloqueadas pelo servidor WA — política do servidor, sem workaround |
|
||
| Conta pessoal (não-Business) | Servidor WA pode rejeitar o envio de mensagens interativas |
|
||
| Conta Business não verificada | Funciona, mas pode ter limitações de alcance |
|