/flows), pela API REST (/v1/flows), ou anexar um Flow a um template com um botão FLOW.
Flows existem apenas em números Meta (API Oficial). Eles pertencem à conta WhatsApp Business (WABA), não a um número — então todo número daquela WABA envia os mesmos Flows.
Dois tipos de Flow
A diferença é se o seu backend participa enquanto o formulário está aberto.
Um Flow
NAVIGATE é entregue, preenchido e enviado uma vez. Você recebe a resposta inteira no fim. A maioria dos Flows é assim, e não precisa de nada além do Flow JSON.
Um Flow data_exchange responde no meio do formulário: o cliente escolhe a data e a tela seguinte mostra os horários que estão de fato livres. Isso exige um endpoint HTTPS que a Meta chama a cada tela — e a Meta cifra cada uma dessas chamadas.
Ciclo de vida
1
Criar
O Flow nasce em
DRAFT. Dê um nome e uma ou mais categorias (SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY, OTHER).2
Subir o Flow JSON
As telas são um documento JSON. A Meta responde
200 com a lista de erros de validação em vez de recusar — então resposta verde não significa que o documento compilou. Leia sempre validationErrors.3
Publicar
Publicar torna o Flow enviável — e é irreversível do lado da Meta. Um Flow publicado nunca mais pode ser editado.
4
Clonar para fazer uma nova versão
Como publicar é definitivo, a forma de mudar um Flow no ar é clonar. O clone é um Flow novo, com id novo, e o Pilot Status grava a linhagem para você seguir a cadeia de uma versão à seguinte.
5
Depreciar
Aposentar um Flow não o apaga, e não apaga as respostas que já foram enviadas. Você pode nomear o Flow que o substitui, que é o que mantém a cadeia de versões legível.
Enviar um Flow
Anexe o Flow a um template usando um botão FLOW. O botão amarra porflow_id, por flow_name ou por flow_json embutido. Todo envio cunha um flow_token novo — um valor de uso único que liga aquele envio específico à resposta que chega depois.
Receber a resposta
Quando o cliente envia, a Meta manda umnfm_reply com o flow_token e, num Flow NAVIGATE, o formulário preenchido como string JSON. Há duas formas de ler:
- Ao vivo — o evento de webhook
flow.response_received, já com as respostas parseadas. Veja Receber as respostas de um Flow. - Depois —
GET /v1/flows/{id}/responses, já ligado ao contato que respondeu.
As respostas ficam guardadas por uma janela de retenção e são podadas depois. A janela é aplicada na leitura, então uma resposta vencida nunca é servida — mesmo que o podador ainda não tenha chegado nela.
data_exchange: o Pilot Status cuida da criptografia
Essa é a parte que costuma impedir os times de colocar um Flow interativo no ar.
A Meta cifra cada chamada ao endpoint: um payload AES-GCM cuja chave chega embrulhada em RSA-2048/OAEP-SHA-256 sob a sua chave pública, e a resposta tem de voltar cifrada com a mesma chave AES sob um IV invertido bit a bit. Errar qualquer detalhe disso produz um Flow que simplesmente não avança, sem erro nenhum dos dois lados.
Então o endpoint é o Pilot Status. A gente guarda a chave, decifra a requisição da Meta e repassa JSON puro por HTTPS para um webhook seu. A sua resposta é cifrada na volta. Você nunca implementa RSA, AES-GCM nem o IV invertido.
Duas configurações em dois níveis, e um registro — a chave é do número, a URL de encaminhamento é do Flow, e o
endpoint_uri é escrito na Meta. Flows NAVIGATE não precisam de nenhum dos três. Os três ficam na tela de Flows do painel, e cada um tem superfície própria na API pública: GET / POST /v1/numbers/{id}/flow-endpoint-key, GET / PUT / PATCH /v1/flows/{id}/endpoint, e POST /v1/flows/{id}/endpoint para o registro — todas atrás de flows:manage. Nenhuma das URLs aparece no GET /v1/flows, porque o token dentro dela é uma credencial — veja Conecte a sua API a um Flow.1
1. Registrar uma chave para o número
Ou gere uma — o Pilot Status cria um par RSA-2048, guarda a metade privada cifrada e registra a pública na Meta para aquele número — ou importe a chave privada que você já usa, o que não muda nada na Meta. Gerar substitui a chave que a Meta tem, porque a Meta guarda exatamente uma por número.
2
2. Configurar a URL do seu webhook no Flow
É para onde repassamos o JSON decifrado. Fica configurado por Flow, porque um número serve vários formulários e cada um costuma ser um serviço diferente do seu lado.
3
3. Registrar a gente na Meta
Registar na Meta, no painel, escreve a nossa URL de endpoint no
endpoint_uri do Flow, na Meta — assim como o POST /v1/flows/{id}/endpoint. Essa URL tem dois segmentos — um identificando o número, outro identificando o Flow — porque a requisição da Meta não diz a qual Flow ela pertence. Salvar a URL no passo 2 não faz isso por você, de propósito: como efeito colateral de um rename, sequestraria um Flow que já aponta para o endpoint de outra pessoa. Um Flow que já aponta para nós é respondido sem escrita; um que aponta para outro lugar é recusado até você confirmar que quer assumir.O que o seu webhook recebe
action é INIT quando o formulário abre, BACK quando o cliente volta, e data_exchange quando ele envia uma tela.
A requisição vai assinada com HMAC-SHA256 do corpo cru no cabeçalho x-pilot-status-signature — o mesmo cabeçalho e o mesmo esquema dos webhooks de saída do Pilot Status, então quem já valida aqueles não precisa de um segundo caminho.
O que o seu webhook precisa devolver
JSON puro, nomeando a próxima tela e os dados dela:SUCCESS:
Responda em poucos segundos. A Meta segura a requisição aberta enquanto uma pessoa olha para um spinner, e não há retentativa — resposta lenta é tela falhada. Faça o trabalho demorado depois de responder, não antes.
O que fica guardado das trocas
Toda trocadata_exchange que deciframos fica guardada ao lado da resposta que o formulário acaba produzindo, e é publicada em submitted — uma entrada por troca, tanto no evento flow.response_received quanto no GET /v1/flows/{id}/responses. Dentro do Pilot Status, é o único lugar onde as respostas de um Flow data_exchange existem: o nfm_reply que a Meta manda no fim carrega o flow_token e mais nada, então o response fica vazio justamente nos Flows cujas telas mais coletaram.
O que fica guardado é a requisição que a Meta manda para nós — o que a pessoa digitou, tela a tela. O que o seu endpoint respondeu, não: o data que você devolve, extension_message_response incluído, é cifrado de volta para a Meta e não é anotado em lugar nenhum. INIT e BACK também não — o INIT devolve os params que semeamos no envio e o BACK é navegação, então nenhum dos dois é algo que uma pessoa digitou, e a lista é legitimamente mais curta que os traços do seu próprio endpoint.
Nada some em silêncio. Uma troca grande demais para guardar mantém o lugar dela na lista e diz
DATA_TOO_LARGE; uma que não conseguimos ler diz UNREADABLE; o teto de 50 trocas por formulário fecha a lista com uma entrada final dizendo LIMIT_REACHED, e nada depois dela foi guardado. A lista também é append-only: quem volta uma tela e envia de novo produz uma segunda entrada legítima, separada da primeira só pelo horário.screen, at, data, omitted — está na API de Flows.
Quando o seu webhook falha
Se o seu servidor está fora, estoura o tempo ou responde algo inutilizável, o Pilot Status não repassa a falha como tela quebrada. O cliente vê um erro genérico na tela em que já está, e pode tentar de novo — nada do que ele digitou se perde, e nada sobre a sua infraestrutura é mostrado a ele.Anexos, e o que a Meta não deixa você desenhar
UmPhotoPicker ou um DocumentPicker coloca um arquivo na submissão. Quatro restrições decidem se o Flow que você está desenhando é construível:
O limite de um picker é da Meta, e estas são as palavras dela. Subir um Flow JSON com um
PhotoPicker e um DocumentPicker na mesma tela é recusado com 422 FLOW_JSON_INVALID, e a entrada da Meta volta verbatim dentro de validationErrors:pointers nomeia os componentes recusados. Mover o segundo picker para uma tela só dele faz o mesmo documento compilar, com validationErrors: []. No SDK de Node esse mesmo array se lê com flowJsonValidationErrors(err).Medido contra a Meta em 6 de setembro de 2026. O código, o texto e o limite são da Meta e ela pode mudá-los sem avisar ninguém — se isto deixar de bater com o que você recebe, acredite na resposta da Meta, não nesta página.flowMedia no webhook, media na superfície de leitura, e por que a URL já existe antes dos bytes — está em Receber as respostas de um Flow.
Saúde e estado
Cada Flow carrega um estado (DRAFT, PUBLISHED, DEPRECATED, BLOCKED, THROTTLED), a versão do Flow JSON que a Meta compilou, e um veredito de saúde.
A Meta descontinuou a Flows Metrics API em 30/04/2026, sem sucessor. O webhook
flows passou a ser o único canal que reporta um Flow sendo limitado ou uma chave pública vencendo — o Pilot Status assina esse webhook e grava o que ele diz no Flow, para o sinal sobreviver à janela dos logs.Relacionado
- Receber as respostas de um Flow
- Flows com dados ao vivo — configurar o
data_exchange, em linguagem de produto - Conecte a sua API a um Flow — o contrato que o seu backend implementa
- API de Flows
- Templates
- Webhooks