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 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.

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 vá até Endpoint.
  2. 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.
  3. 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.
  4. 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ç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.