Endpoints de WhatsApp Flows
Flows com telas dinâmicas precisam de um endpoint de troca de dados que o WhatsApp chama a cada transição de tela, com todo o tráfego criptografado com um par de chaves da empresa. A Turn hospeda esse endpoint e gerencia a criptografia por você, então você nunca manipula material de chaves.
Comece clicando em Add endpoint abaixo do editor de Flow JSON. Ele
adiciona data_api_version: "3.0" e um routing_model vazio (ambos
exigidos pela Meta) ao Flow JSON e salva o flow, e a interface da Turn
exibirá novos campos de formulário para ajudar você a configurar seu
endpoint.
Flows estáticos não precisam de nada disto. Um flow sem
data_api_version no Flow JSON é conduzido inteiramente no dispositivo
pelo WhatsApp. Nenhum endpoint é chamado, e esta página não se aplica. A
configuração abaixo é apenas para flows que usam o canal de troca de dados.
WhatsApp Flows é um recurso fornecido pela Meta. Para especificações de flows, tipos de tela e componentes, consulte a documentação oficial de WhatsApp Flows. O guia Implementing Your Flow Endpoint da Meta descreve o protocolo de endpoint que a Turn implementa por você.
Configuração
- Abra seu flow em Conteúdo → Flows e vá até Endpoint.
- Clique em Configurar criptografia (uma vez por número). A Turn gera o par de chaves, guarda a chave privada em um cofre externo seguro e registra a chave pública na Meta.
- Em um flow em rascunho, clique em Registrar na Meta. Isso registra o endpoint de troca de dados do flow (hospedado pela Turn) na Meta — obrigatório antes de publicar o flow.
- Defina uma URL de encaminhamento (ou escolha um app instalado da Turn). É aí que as requisições do seu flow são respondidas. Sem ela, as transições de tela falham.
Traga sua própria chave de criptografia
Se você já registrou sua própria chave pública na Meta para este
número, a Turn nunca fica com a chave privada correspondente e não
consegue descriptografar suas requisições — então nem tenta. O painel de
Endpoint mostra Sua própria chave de criptografia, oculta a
configuração de criptografia (que sobrescreveria sua chave) e repassa cada
requisição para sua URL de encaminhamento exatamente como o WhatsApp
enviou: ainda criptografada, com o próprio cabeçalho x-hub-signature-256
da Meta intacto. Seu endpoint descriptografa e responde conforme o
protocolo de endpoint da Meta,
e a Turn repassa sua resposta de volta ao WhatsApp sem alterações.
O restante desta página (webhooks descriptografados pela Turn com um
cabeçalho X-Turn-Hook-Signature) só se aplica quando a Turn gerencia a
chave para você.
Webhooks de encaminhamento
A Turn descriptografa cada requisição do WhatsApp e a entrega ao seu endpoint HTTPS como um webhook JSON simples, sem necessidade de código de criptografia do seu lado.
Requisição
Cada requisição é um POST com estes cabeçalhos:
| Cabeçalho | Valor |
|---|---|
Content-Type | application/json |
X-Turn-Hook-Signature | HMAC-SHA256 do corpo bruto da requisição, em Base64 |
X-Turn-Flow-Id | O id do flow (atribuído pela Meta) |
X-Turn-Number-Uuid | O uuid do número |
O corpo é exatamente o que o cliente WhatsApp enviou, já descriptografado:
{
"version": "3.0",
"action": "data_exchange",
"screen": "HELLO",
"data": { "name": "Maria" },
"flow_token": "<token fornecido ao enviar o flow>"
}
action é um de INIT (primeira tela solicitada), data_exchange
(usuário completou uma tela) ou BACK.
A Meta também verifica periodicamente se o endpoint de um flow está no ar
(por exemplo, quando você publica o flow) enviando uma requisição com
action: "ping", e reporta erros do lado do cliente com requisições de
notificação de erro. A Turn responde ambas automaticamente; elas nunca
chegam ao seu endpoint.
Verificando a assinatura
Sua URL de encaminhamento é acessível publicamente, então qualquer um
poderia enviar um POST para ela. Para provar que uma requisição veio mesmo
da Turn, todo webhook é assinado: o cabeçalho X-Turn-Hook-Signature
carrega o HMAC-SHA256 (em Base64) do corpo bruto da requisição, usando um
segredo que só você e a Turn conhecem. Recalcule a assinatura e compare
antes de confiar no payload.
O segredo é exibido em Mostrar segredo de assinatura no painel Endpoint do flow:
import base64, hashlib, hmac
esperado = base64.b64encode(
hmac.new(segredo.encode(), corpo_bruto, hashlib.sha256).digest()
).decode()
valido = hmac.compare_digest(esperado, request.headers["X-Turn-Hook-Signature"])
Resposta
Responda em até 8 segundos: o WhatsApp bloqueia a tela do usuário enquanto espera:
-
200com a próxima tela:{ "screen": "ID_DA_TELA", "data": {} } -
200com o payload terminal para encerrar o flow:{"screen": "SUCCESS","data": {"extension_message_response": {"params": { "flow_token": "<flow_token>" }}}} -
427quando oflow_tokennão é mais válido (o cliente descarta a sessão do flow). -
Qualquer outro status (ou timeout) mostra um erro transitório ao usuário, que pode tentar novamente a partir da mesma tela.
Exemplo completo
Um flow hello-world e o servidor Python/Flask que o responde.
Crie um flow com este Flow JSON — uma tela HELLO com um campo de nome
cujo Footer envia pelo canal de dados:
{
"version": "7.2",
"data_api_version": "3.0",
"routing_model": {},
"screens": [
{
"id": "HELLO",
"title": "Hello World",
"terminal": true,
"layout": {
"type": "SingleColumnLayout",
"children": [
{
"type": "Form",
"name": "hello_form",
"children": [
{
"type": "TextInput",
"input-type": "text",
"label": "Your name",
"name": "name",
"required": true
},
{
"type": "Footer",
"label": "Submit",
"on-click-action": {
"name": "data_exchange",
"payload": { "name": "${form.name}" }
}
}
]
}
]
}
}
]
}
O servidor responde ao INIT com essa tela e registra no log os dados
enviados no data_exchange. Se você usar seu próprio flow, defina
TELA_INICIAL com o id da tela inicial desse flow:
import base64
import hashlib
import hmac
import os
from flask import Flask, jsonify, request
SECRET = os.environ["TURN_SIGNING_SECRET"]
# O id da tela inicial do seu flow, conforme definido no Flow JSON
TELA_INICIAL = "HELLO"
app = Flask(__name__)
def assinado(req):
esperado = base64.b64encode(
hmac.new(SECRET.encode(), req.get_data(), hashlib.sha256).digest()
).decode()
return hmac.compare_digest(
esperado, req.headers.get("X-Turn-Hook-Signature", "")
)
@app.post("/whatsapp-flows")
def flow_endpoint():
if not assinado(request):
return "", 401
payload = request.get_json()
if payload["action"] == "INIT":
# Primeira tela solicitada. Se a sua tela declara dados dinâmicos
# no Flow JSON, inclua-os em "data".
return jsonify({"screen": TELA_INICIAL, "data": {}})
if payload["action"] == "data_exchange" and payload["screen"] == TELA_INICIAL:
# Sua lógica de negócio entra aqui: payload["data"] contém as respostas
print("recebido:", payload["data"])
# Payload terminal: encerra o flow no dispositivo do usuário
return jsonify(
{
"screen": "SUCCESS",
"data": {
"extension_message_response": {
"params": {"flow_token": payload["flow_token"]}
}
},
}
)
# Qualquer outra coisa significa que o estado da sessão não faz mais
# sentido: 427 diz ao cliente para descartar a sessão do flow
return "", 427
Execute localmente
Salve o arquivo como app.py e então:
python3 -m venv .venv && source .venv/bin/activate
pip install flask
# exponha a porta 5000 publicamente, por exemplo com ngrok
ngrok http 5000
# o segredo vem de "Mostrar segredo de assinatura" no painel Endpoint
TURN_SIGNING_SECRET="<segredo>" flask --app app run --port 5000
Cole a URL pública do túnel com o caminho /whatsapp-flows (por exemplo
https://<seu-tunel>.ngrok-free.app/whatsapp-flows) no campo de URL de
encaminhamento do flow, e publique o flow caso ainda não esteja publicado.
Publicar exige antes os passos de Configuração: a Meta
se recusa a publicar ou enviar um flow com endpoint enquanto o
endpoint_uri não estiver definido, então um flow que pulou
Registrar na Meta falha exatamente com esse erro.
Envie o flow pelo inbox ou por um journey e percorra as telas em um celular (os aplicativos desktop do WhatsApp não abrem flows de forma confiável) — os dados enviados aparecem no log do servidor, sem nenhum código de criptografia no arquivo.
flask run inicia o servidor de desenvolvimento do Flask, que é
exatamente o adequado para este passo a passo. Ao publicar em produção,
execute a aplicação em um servidor WSGI de produção como o gunicorn — ou
implemente o mesmo contrato de webhook na linguagem que a sua stack já
usa.
Respondendo com um app da Turn
Em vez de hospedar seu próprio servidor, um
app da Turn instalado pode responder o
flow: escolha-o no seletor de apps do painel Endpoint, que aponta a URL
de encaminhamento para o endpoint HTTP público do app. Cada requisição
chega ao app como um evento http_request cujo caminho termina em
/whatsapp_flows, e o app responde com o mesmo contrato de resposta
acima:
local App = {}
local turn = require("turn")
local router = turn.http.server.router.new("Flows endpoint")
router:post("/whatsapp_flows")(function(request, response)
local payload = request.form
if payload.action == "INIT" then
return response:json({ screen = "HELLO", data = {} })
end
-- Sua lógica de negócio entra aqui: payload.data contém as respostas
return response:json({
screen = "SUCCESS",
data = {
extension_message_response = {
params = { flow_token = payload.flow_token },
},
},
})
end)
function App.on_event(app, number, event, data)
if event == "http_request" then
return true, router:handle(data)
end
-- Nada a fazer para eventos de install, uninstall ou config
return true
end
return App
Para empacotar: turn-app new flows_endpoint (o CLI do SDK, veja o guia
Getting Started dos apps), cole o código em
flows_endpoint.lua, rode make build e envie o ZIP gerado em
Apps → Upload app.
Esses encaminhamentos são assinados como qualquer outro: o cabeçalho
X-Turn-Hook-Signature está entre os cabeçalhos da requisição, e
turn.crypto.verify_hmac_sha256 pode verificá-lo. Mais detalhes na
referência da HTTP Server API.