Mensagens interativas
Mensagens com botões, lista, Flow e outras ações. Vão no mesmo endpoint de Enviar mensagens, com type: "interactive"; o interactive.type escolhe a variante. A resposta do envio é a de sempre (200 com o wamid em messages[0].id).
Duas regras valem para todas:
- Só dentro da janela de 24 h do cliente (Janela de atendimento de 24 horas). Fora dela, use um modelo (Modelos de mensagem (templates)). A janela não isenta de cobrança: a Meta cobra cada mensagem interativa como mensagem de serviço (Cobrança).
- Quando o cliente responde, o webhook
messagesentrega uma mensagemtype: "interactive"com o objeto da resposta.context.idé owamidda mensagem interativa que ele respondeu (Receber mensagens e status).
Botões de resposta#
De 1 a 3 botões. body.text tem até 1024 caracteres; header (texto, imagem, vídeo ou documento) e footer (até 60) são opcionais. Em cada botão, reply.id (até 256 caracteres, único) volta no webhook, e reply.title (até 20 caracteres, único) é o texto exibido.
Quando o cliente toca em Confirmar, chega um button_reply com o id e o title do botão:
curl
curl -X POST 'https://api.synergyconnect.com.br/v25.0/106540352242922/messages' \
-H "Authorization: Bearer $SYNERGY_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "button",
"header": {
"type": "text",
"text": "Pedido 42"
},
"body": {
"text": "Podemos confirmar a entrega para amanhã?"
},
"footer": {
"text": "Loja Exemplo"
},
"action": {
"buttons": [
{
"type": "reply",
"reply": {
"id": "confirm",
"title": "Confirmar"
}
},
{
"type": "reply",
"reply": {
"id": "reschedule",
"title": "Remarcar"
}
}
]
}
}
}'JavaScript
const res = await fetch('https://api.synergyconnect.com.br/v25.0/106540352242922/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SYNERGY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "button",
"header": {
"type": "text",
"text": "Pedido 42"
},
"body": {
"text": "Podemos confirmar a entrega para amanhã?"
},
"footer": {
"text": "Loja Exemplo"
},
"action": {
"buttons": [
{
"type": "reply",
"reply": {
"id": "confirm",
"title": "Confirmar"
}
},
{
"type": "reply",
"reply": {
"id": "reschedule",
"title": "Remarcar"
}
}
]
}
}
}),
});
console.log(res.status, await res.json());Python
import os
import requests
res = requests.post(
"https://api.synergyconnect.com.br/v25.0/106540352242922/messages",
headers={"Authorization": f"Bearer {os.environ['SYNERGY_API_KEY']}"},
json={
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "button",
"header": {
"type": "text",
"text": "Pedido 42",
},
"body": {
"text": "Podemos confirmar a entrega para amanhã?",
},
"footer": {
"text": "Loja Exemplo",
},
"action": {
"buttons": [
{
"type": "reply",
"reply": {
"id": "confirm",
"title": "Confirmar",
},
},
{
"type": "reply",
"reply": {
"id": "reschedule",
"title": "Remarcar",
},
},
],
},
},
},
)
print(res.status_code, res.json()){
"object": "whatsapp_business_account",
"entry": [
{
"id": "102290129340398",
"time": 1767225600,
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "554430000000",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"profile": {
"name": "Maria Souza"
},
"wa_id": "5511999999999"
}
],
"messages": [
{
"from": "5511999999999",
"id": "wamid.HBgNNTUxMTk5OTk5OTk5ORUCABIYFjNFQjBDNUE2RjQ3RjE4QjdBMzQ1AA==",
"timestamp": "1767225600",
"type": "interactive",
"context": {
"from": "554430000000",
"id": "wamid.HBgNNTUxMTk5OTk5OTk5ORUCABEYEjdGQjI0QzE4RTlBMDVENzQxAA=="
},
"interactive": {
"type": "button_reply",
"button_reply": {
"id": "confirm",
"title": "Confirmar"
}
}
}
]
}
}
]
}
]
}Lista#
Um botão que abre uma lista de opções: até 10 seções e 10 linhas no total. action.button (até 20 caracteres) é o texto do botão que abre a lista. O título da seção (até 24 caracteres) é obrigatório quando há mais de uma seção. Em cada linha, id (até 200 caracteres) volta no webhook, title tem até 24 e description (opcional) até 72 caracteres. O header, se houver, é só texto.
A escolha chega como list_reply:
curl
curl -X POST 'https://api.synergyconnect.com.br/v25.0/106540352242922/messages' \
-H "Authorization: Bearer $SYNERGY_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "list",
"header": {
"type": "text",
"text": "Frete"
},
"body": {
"text": "Escolha como quer receber."
},
"footer": {
"text": "Prazos em dias úteis"
},
"action": {
"button": "Ver opções",
"sections": [
{
"title": "Entrega",
"rows": [
{
"id": "express",
"title": "Expressa",
"description": "1 a 2 dias"
},
{
"id": "standard",
"title": "Normal",
"description": "3 a 5 dias"
}
]
},
{
"title": "Retirada",
"rows": [
{
"id": "pickup",
"title": "Retirar na loja",
"description": "Hoje, a partir das 14 h"
}
]
}
]
}
}
}'JavaScript
const res = await fetch('https://api.synergyconnect.com.br/v25.0/106540352242922/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SYNERGY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "list",
"header": {
"type": "text",
"text": "Frete"
},
"body": {
"text": "Escolha como quer receber."
},
"footer": {
"text": "Prazos em dias úteis"
},
"action": {
"button": "Ver opções",
"sections": [
{
"title": "Entrega",
"rows": [
{
"id": "express",
"title": "Expressa",
"description": "1 a 2 dias"
},
{
"id": "standard",
"title": "Normal",
"description": "3 a 5 dias"
}
]
},
{
"title": "Retirada",
"rows": [
{
"id": "pickup",
"title": "Retirar na loja",
"description": "Hoje, a partir das 14 h"
}
]
}
]
}
}
}),
});
console.log(res.status, await res.json());Python
import os
import requests
res = requests.post(
"https://api.synergyconnect.com.br/v25.0/106540352242922/messages",
headers={"Authorization": f"Bearer {os.environ['SYNERGY_API_KEY']}"},
json={
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "list",
"header": {
"type": "text",
"text": "Frete",
},
"body": {
"text": "Escolha como quer receber.",
},
"footer": {
"text": "Prazos em dias úteis",
},
"action": {
"button": "Ver opções",
"sections": [
{
"title": "Entrega",
"rows": [
{
"id": "express",
"title": "Expressa",
"description": "1 a 2 dias",
},
{
"id": "standard",
"title": "Normal",
"description": "3 a 5 dias",
},
],
},
{
"title": "Retirada",
"rows": [
{
"id": "pickup",
"title": "Retirar na loja",
"description": "Hoje, a partir das 14 h",
},
],
},
],
},
},
},
)
print(res.status_code, res.json()){
"object": "whatsapp_business_account",
"entry": [
{
"id": "102290129340398",
"time": 1767225600,
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "554430000000",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"profile": {
"name": "Maria Souza"
},
"wa_id": "5511999999999"
}
],
"messages": [
{
"from": "5511999999999",
"id": "wamid.HBgNNTUxMTk5OTk5OTk5ORUCABIYFjNFQjBDNUE2RjQ3RjE4QjdBMzQ1AA==",
"timestamp": "1767225600",
"type": "interactive",
"context": {
"from": "554430000000",
"id": "wamid.HBgNNTUxMTk5OTk5OTk5ORUCABEYEjdGQjI0QzE4RTlBMDVENzQxAA=="
},
"interactive": {
"type": "list_reply",
"list_reply": {
"id": "express",
"title": "Expressa",
"description": "1 a 2 dias"
}
}
}
]
}
}
]
}
]
}Flow#
Abre um formulário do WhatsApp Flows. Precisa da feature waFlows no plano e, num Flow com endpoint da Synergy, de um flow_token emitido por POST /v1/numbers/{phone_number_id}/flows/{flow_id}/token. O passo a passo completo está em WhatsApp Flows.
Parâmetros: flow_message_version é sempre "3"; flow_id ou flow_name (nunca os dois); flow_cta é o texto do botão (até 30 caracteres, sem emoji); flow_action é navigate (padrão, abre na tela de flow_action_payload.screen, com os data de entrada) ou data_exchange (pede a primeira tela ao endpoint do Flow); mode é published (padrão) ou draft.
Quando o cliente envia o formulário, chega um nfm_reply. response_json é um texto com JSON (use JSON.parse ou json.loads): traz o flow_token do envio e um campo por resposta.
curl
curl -X POST 'https://api.synergyconnect.com.br/v25.0/106540352242922/messages' \
-H "Authorization: Bearer $SYNERGY_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "flow",
"header": {
"type": "text",
"text": "Cadastro"
},
"body": {
"text": "Complete seu cadastro em 1 minuto."
},
"footer": {
"text": "Leva menos de 1 minuto"
},
"action": {
"name": "flow",
"parameters": {
"flow_message_version": "3",
"flow_id": "9876543210",
"flow_cta": "Abrir cadastro",
"flow_token": "cadastro-pedido-42",
"flow_action": "navigate",
"flow_action_payload": {
"screen": "SIGN_UP",
"data": {
"order_id": "42"
}
}
}
}
}
}'JavaScript
const res = await fetch('https://api.synergyconnect.com.br/v25.0/106540352242922/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SYNERGY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "flow",
"header": {
"type": "text",
"text": "Cadastro"
},
"body": {
"text": "Complete seu cadastro em 1 minuto."
},
"footer": {
"text": "Leva menos de 1 minuto"
},
"action": {
"name": "flow",
"parameters": {
"flow_message_version": "3",
"flow_id": "9876543210",
"flow_cta": "Abrir cadastro",
"flow_token": "cadastro-pedido-42",
"flow_action": "navigate",
"flow_action_payload": {
"screen": "SIGN_UP",
"data": {
"order_id": "42"
}
}
}
}
}
}),
});
console.log(res.status, await res.json());Python
import os
import requests
res = requests.post(
"https://api.synergyconnect.com.br/v25.0/106540352242922/messages",
headers={"Authorization": f"Bearer {os.environ['SYNERGY_API_KEY']}"},
json={
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "flow",
"header": {
"type": "text",
"text": "Cadastro",
},
"body": {
"text": "Complete seu cadastro em 1 minuto.",
},
"footer": {
"text": "Leva menos de 1 minuto",
},
"action": {
"name": "flow",
"parameters": {
"flow_message_version": "3",
"flow_id": "9876543210",
"flow_cta": "Abrir cadastro",
"flow_token": "cadastro-pedido-42",
"flow_action": "navigate",
"flow_action_payload": {
"screen": "SIGN_UP",
"data": {
"order_id": "42",
},
},
},
},
},
},
)
print(res.status_code, res.json()){
"object": "whatsapp_business_account",
"entry": [
{
"id": "102290129340398",
"time": 1767225600,
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "554430000000",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"profile": {
"name": "Maria Souza"
},
"wa_id": "5511999999999"
}
],
"messages": [
{
"from": "5511999999999",
"id": "wamid.HBgNNTUxMTk5OTk5OTk5ORUCABIYFjNFQjBDNUE2RjQ3RjE4QjdBMzQ1AA==",
"timestamp": "1767225600",
"type": "interactive",
"context": {
"from": "554430000000",
"id": "wamid.HBgNNTUxMTk5OTk5OTk5ORUCABEYEjdGQjI0QzE4RTlBMDVENzQxAA=="
},
"interactive": {
"type": "nfm_reply",
"nfm_reply": {
"name": "flow",
"body": "Sent",
"response_json": "{\"flow_token\":\"cadastro-pedido-42\",\"name\":\"Maria Souza\",\"email\":\"maria@example.com\"}"
}
}
}
]
}
}
]
}
]
}Botão de URL (cta_url)#
O OpenAPI v25 da Meta não descreve a interativa
cta_url, então ela não está nos schemas da referência desta API. A Synergy repassa o corpo como vier, e ela funciona se a Meta a aceitar no número. Os campos abaixo são os da documentação pública da Meta para esta variante: confirme lá antes de ir para produção.
Um único botão que abre uma URL. Não há resposta no webhook: o toque abre o navegador do cliente e nada volta para a integração.
curl
curl -X POST 'https://api.synergyconnect.com.br/v25.0/106540352242922/messages' \
-H "Authorization: Bearer $SYNERGY_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "cta_url",
"body": {
"text": "Acompanhe o pedido 42 pelo site."
},
"action": {
"name": "cta_url",
"parameters": {
"display_text": "Ver pedido",
"url": "https://www.example.com/pedidos/42"
}
}
}
}'JavaScript
const res = await fetch('https://api.synergyconnect.com.br/v25.0/106540352242922/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SYNERGY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "cta_url",
"body": {
"text": "Acompanhe o pedido 42 pelo site."
},
"action": {
"name": "cta_url",
"parameters": {
"display_text": "Ver pedido",
"url": "https://www.example.com/pedidos/42"
}
}
}
}),
});
console.log(res.status, await res.json());Python
import os
import requests
res = requests.post(
"https://api.synergyconnect.com.br/v25.0/106540352242922/messages",
headers={"Authorization": f"Bearer {os.environ['SYNERGY_API_KEY']}"},
json={
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "cta_url",
"body": {
"text": "Acompanhe o pedido 42 pelo site.",
},
"action": {
"name": "cta_url",
"parameters": {
"display_text": "Ver pedido",
"url": "https://www.example.com/pedidos/42",
},
},
},
},
)
print(res.status_code, res.json())Pedido de localização#
Igual ao
cta_url: a variantelocation_request_messagenão está no OpenAPI v25 da Meta nem nos schemas daqui, e é repassada como vier. Confira os campos na documentação da Meta.
Mostra um botão "Enviar localização". Quando o cliente toca e envia, a resposta é uma mensagem comum type: "location" no webhook messages (veja Receber mensagens e status): não há button_reply.
curl
curl -X POST 'https://api.synergyconnect.com.br/v25.0/106540352242922/messages' \
-H "Authorization: Bearer $SYNERGY_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "location_request_message",
"body": {
"text": "Para onde devemos entregar? Envie a sua localização."
},
"action": {
"name": "send_location"
}
}
}'JavaScript
const res = await fetch('https://api.synergyconnect.com.br/v25.0/106540352242922/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SYNERGY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "location_request_message",
"body": {
"text": "Para onde devemos entregar? Envie a sua localização."
},
"action": {
"name": "send_location"
}
}
}),
});
console.log(res.status, await res.json());Python
import os
import requests
res = requests.post(
"https://api.synergyconnect.com.br/v25.0/106540352242922/messages",
headers={"Authorization": f"Bearer {os.environ['SYNERGY_API_KEY']}"},
json={
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "location_request_message",
"body": {
"text": "Para onde devemos entregar? Envie a sua localização.",
},
"action": {
"name": "send_location",
},
},
},
)
print(res.status_code, res.json())Produto e catálogo#
O catálogo precisa estar ligado à conta do WhatsApp Business; catalog_id e product_retailer_id (o SKU) são os do catálogo na Meta. Há três variantes, todas descritas no OpenAPI da Meta:
product: um produto. Semheader; obodyé opcional.product_list: até 30 produtos em até 10 seções. Oheaderde texto é obrigatório.catalog_message: um botão que abre o catálogo inteiro, com a miniatura de um produto.
Os outros dois, em resumo (mesmo envio, mudando só o interactive):
Quando o cliente monta o carrinho e envia, chega uma mensagem type: "order" (catalog_id, product_items com quantity, item_price e currency): veja o exemplo em Receber mensagens e status. Se o cliente escreve pelo botão "Mensagem" de um produto, context.referred_product traz o catálogo e o produto.
curl
curl -X POST 'https://api.synergyconnect.com.br/v25.0/106540352242922/messages' \
-H "Authorization: Bearer $SYNERGY_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "product_list",
"header": {
"type": "text",
"text": "Novidades"
},
"body": {
"text": "Separamos estes para você."
},
"action": {
"catalog_id": "367025965434465",
"sections": [
{
"title": "Tênis",
"product_items": [
{
"product_retailer_id": "tenis-azul-42"
},
{
"product_retailer_id": "tenis-verde-41"
}
]
},
{
"title": "Meias",
"product_items": [
{
"product_retailer_id": "meia-kit-3"
}
]
}
]
}
}
}'JavaScript
const res = await fetch('https://api.synergyconnect.com.br/v25.0/106540352242922/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SYNERGY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "product_list",
"header": {
"type": "text",
"text": "Novidades"
},
"body": {
"text": "Separamos estes para você."
},
"action": {
"catalog_id": "367025965434465",
"sections": [
{
"title": "Tênis",
"product_items": [
{
"product_retailer_id": "tenis-azul-42"
},
{
"product_retailer_id": "tenis-verde-41"
}
]
},
{
"title": "Meias",
"product_items": [
{
"product_retailer_id": "meia-kit-3"
}
]
}
]
}
}
}),
});
console.log(res.status, await res.json());Python
import os
import requests
res = requests.post(
"https://api.synergyconnect.com.br/v25.0/106540352242922/messages",
headers={"Authorization": f"Bearer {os.environ['SYNERGY_API_KEY']}"},
json={
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "5511999999999",
"type": "interactive",
"interactive": {
"type": "product_list",
"header": {
"type": "text",
"text": "Novidades",
},
"body": {
"text": "Separamos estes para você.",
},
"action": {
"catalog_id": "367025965434465",
"sections": [
{
"title": "Tênis",
"product_items": [
{
"product_retailer_id": "tenis-azul-42",
},
{
"product_retailer_id": "tenis-verde-41",
},
],
},
{
"title": "Meias",
"product_items": [
{
"product_retailer_id": "meia-kit-3",
},
],
},
],
},
},
},
)
print(res.status_code, res.json()){
"type": "product",
"body": {
"text": "O modelo que você pediu."
},
"action": {
"catalog_id": "367025965434465",
"product_retailer_id": "tenis-azul-42"
}
}{
"type": "catalog_message",
"body": {
"text": "Veja nosso catálogo e faça o pedido por aqui."
},
"footer": {
"text": "Entregamos em toda a cidade"
},
"action": {
"name": "catalog_message",
"parameters": {
"thumbnail_product_retailer_id": "tenis-azul-42"
}
}
}Pedido de permissão para ligar#
call_permission_request pede ao cliente permissão para a empresa ligar pelo WhatsApp. O corpo é só body e action: { name: "call_permission_request" }.
Tratar a resposta no webhook#
Um único tratador cobre as três respostas. O id que você pôs no botão ou na linha é o que identifica a escolha.
Descarte repetições pelo X-Synergy-Delivery-Id e pelo messages[].id (Webhooks). Os botões de resposta rápida de um modelo chegam diferente, como type: "button": veja Modelos de mensagem (templates).
JavaScript
function onInteractiveReply(message) {
if (message.type !== 'interactive') return;
const { interactive, context } = message; // context.id: o wamid da mensagem que você enviou
if (interactive.type === 'button_reply') return choose(context.id, interactive.button_reply.id);
if (interactive.type === 'list_reply') return choose(context.id, interactive.list_reply.id);
if (interactive.type === 'nfm_reply') {
const answers = JSON.parse(interactive.nfm_reply.response_json);
return submitted(answers.flow_token, answers);
}
}Python
import json
def on_interactive_reply(message):
if message.get("type") != "interactive":
return
interactive = message["interactive"]
context_id = message.get("context", {}).get("id") # o wamid da mensagem que você enviou
kind = interactive["type"]
if kind == "button_reply":
return choose(context_id, interactive["button_reply"]["id"])
if kind == "list_reply":
return choose(context_id, interactive["list_reply"]["id"])
if kind == "nfm_reply":
answers = json.loads(interactive["nfm_reply"]["response_json"])
return submitted(answers["flow_token"], answers)Ficou com dúvida? Escreva para suporte@synergyconnect.com.br.