Pular para o conteúdo principal

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.

nota

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.

cuidado

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

  1. 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.
  2. 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çalhoValor
Content-Typeapplication/json
X-Turn-Hook-SignatureHMAC-SHA256 do corpo bruto da requisição, em Base64
X-Turn-Flow-IdO id do flow (atribuído pela Meta)
X-Turn-Number-UuidO 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:

  • 200 com a próxima tela:

    { "screen": "ID_DA_TELA", "data": {} }
  • 200 com o payload terminal para encerrar o flow:

    {
    "screen": "SUCCESS",
    "data": {
    "extension_message_response": {
    "params": { "flow_token": "<flow_token>" }
    }
    }
    }
  • 427 quando o flow_token nã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.