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 ativando o botão Use an 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. Desativar o botão remove a declaração novamente.
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 ative o botão Use an endpoint abaixo do editor de Flow JSON. A Turn faz o resto automaticamente: gera o par de chaves do número se ainda não houver um (guardando a chave privada em um cofre externo seguro e registrando a chave pública na Meta) e registra o endpoint de troca de dados do flow em rascunho (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.
O par de chaves é por número — uma chave serve todos os flows do número. Admins podem inspecionar ou rotacionar a chave (ou substituir uma chave gerenciada pelo cliente) em Conteúdo → Flows → ⋮ → Criptografia do endpoint.
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 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.
Para entregar a criptografia à Turn, um Admin pode clicar em Usar criptografia gerenciada pela Turn em Conteúdo → Flows → ⋮ → Criptografia do endpoint. A Turn gera um novo par de chaves e registra a chave pública na Meta, substituindo a sua chave — endpoints que descriptografam com a chave antiga param de funcionar, então confira as URLs de encaminhamento dos seus flows depois.
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.