Pular para o conteúdo principal
Sim — um Flow pode consultar a sua API enquanto o cliente preenche o formulário. É isso que é um Flow data_exchange: o cliente escolhe uma data e a tela seguinte mostra os horários que estão de fato livres, porque o seu backend decidiu quais são. Você não implementa nada da criptografia da Meta. O endpoint que a Meta chama é o Pilot Status. Nós descriptografamos a requisição, fazemos um POST de JSON puro para uma URL sua, e criptografamos a sua resposta na volta.
Um Flow NAVIGATE — aquele em que todas as telas estão fixas no Flow JSON — não precisa de nada disto. Tudo nesta página vale só para Flows cujas telas chamam o seu backend. Veja Flows para a diferença.Esta página é o contrato requisição por requisição. Para o mesmo recurso em linguagem de produto — o que configurar, em que ordem, e o que uma pessoa vê quando está errado — leia Flows com dados ao vivo.

O formato de uma troca

1

O cliente preenche uma tela

A Meta manda uma requisição criptografada — AES-GCM, com a chave envelopada em RSA-OAEP sob a chave pública registrada para aquele telefone — para o endpoint registrado no Flow, que somos nós.
2

Nós descriptografamos e encaminhamos

Um POST de JSON puro para a sua URL, com um cabeçalho de assinatura HMAC. Nada de RSA, AES-GCM ou IV invertido do seu lado.
3

Você responde com a próxima tela

200 com { "screen": "...", "data": { ... } }.
4

Nós criptografamos a resposta

A Meta renderiza a tela que você nomeou.

O que fazemos POST para você

action nunca é null, e uma requisição sem ele nunca é encaminhada. Toda chamada que a Meta faz carrega INIT, BACK, data_exchange ou ping. Um payload descriptografado que não traga nenhum deles é descartado antes de chegar até você, porque encaminhá-lo colocaria a nossa assinatura em algo que não é uma troca de Flow — e o seu endpoint não teria como distingui-lo de um que assinamos de propósito.
Um arquivo escolhido num PhotoPicker ou DocumentPicker chega até nós NESTA troca, e nunca no nfm_reply. É por isso que um Flow sem endpoint não recebe anexo nenhum. O Pilot Status re-hospeda o arquivo e o publica na submissão como flowMedia / media — o evento é emitido assim que a submissão é gravada e a re-hospedagem roda depois dele, então o status normalmente vem PENDING quando o evento cai, e o flowMedia[].url é um link que já funciona: buscá-lo espera a transferência em vez de dar 404. O formato, e o que fazer com isso, estão em Receber as respostas de um Flow. Os limites da própria Meta (um picker por tela, 25 MiB, 20 dias no CDN dela) estão em Flows.

A assinatura

Quando o Flow tem um segredo de assinatura, o encaminhamento leva o cabeçalho x-pilot-status-signature: o HMAC-SHA256 em hexadecimal do corpo cru da requisição, com esse segredo como chave. É o mesmo cabeçalho e o mesmo esquema dos webhooks de saída do Pilot Status — se você já valida aqueles, não precisa de um segundo caminho de código. Calcule o HMAC sobre os bytes como recebidos, antes de qualquer parse de JSON.
Sem segredo configurado, não há cabeçalho. O encaminhamento acontece assim mesmo, sem assinatura, e aí qualquer um que alcance a sua URL pode postar um corpo plausível nela. Configure o segredo.

O que você tem de devolver

JSON puro, 2xx, nomeando a próxima tela:
Para encerrar o Flow, devolva a tela reservada SUCCESS. ⚠️ Este formato é convenção da Meta, não nossa: nós repassamos screen e data à Meta intactos, portanto extension_message_response não aparece em lado nenhum da Pilot Status — não o procure na nossa referência de API.
O que volta na submissão é só o que você nomeou em params, e as respostas não estão ali. A Meta desembrulha esse objeto no nfm_reply, então, num Flow data_exchange, o data.response publica exatamente o formato acima e mais nada — {"flow_token": "a1b2c3..."} — enquanto a mesma submissão já tinha entregado {"observacao": "Aobaaa", "foto": [ … ]} ao seu endpoint. As respostas foram para você, tela a tela. Elas nunca voltaram pela Meta.Leia-as em data.submitted, que carrega as requisições data_exchange que encaminhamos, cada uma com a sua tela e o seu horário — veja Receber as respostas de um Flow. Um Flow comum, sem endpoint, é o outro caso: nada recolheu as respostas no caminho, então ali o response é onde estão todas elas.
Um 200 que não nomeia screen é recusado de propósito. É o único modo de falha que parece sucesso do seu lado e é invisível do lado do cliente: a Meta renderiza um payload sem tela como nada, então a pessoa fica diante de um formulário que nunca avança e nunca dá erro.Em vez disso, tratamos exatamente como uma chamada falha — o cliente recebe um erro repetível sobre a tela em que já está, e a troca fica registrada como falha que você consegue encontrar.

Responda rápido

A Meta segura a requisição aberta enquanto uma pessoa olha para um spinner, e não há retry — uma resposta lenta é uma tela falha, não uma tela atrasada. O nosso encaminhamento é abandonado depois de alguns segundos (8 s por padrão), deliberadamente mais apertado que o orçamento dos webhooks comuns: aqueles são notificações que ninguém está esperando. Faça o trabalho lento depois de responder.

O que resolvemos sem chamar você

O ping da Meta nunca chega ao seu endpoint. É a checagem de saúde da Meta, e nós mesmos respondemos. Uma checagem de saúde que depende de um terceiro estar acordado reporta a coisa errada para a parte errada — o seu servidor fora do ar num deploy faria a Meta marcar o endpoint como não saudável para todos os Flows daquele número.Portanto: não implemente ping, e não espere vê-lo nos seus logs.

Quando a sua API falha

Seja qual for a causa — fora do ar, timeout, não-2xx, corpo impossível de parsear, sem screen — o cliente nunca vê a sua infraestrutura e nunca vê uma tela quebrada.
Uma falha do seu lado nunca é reportada à Meta como problema de chave. A resposta 421, que diz à Meta “baixe a minha chave pública de novo”, é devolvida por exatamente uma coisa: não conseguimos descriptografar. A Meta cacheia essa busca, então usá-la para uma indisponibilidade sua faria a mentira sobreviver à indisponibilidade.

Ver o que aconteceu

A tela do Flow no painel lista as trocas recentes: a ação, a tela, o desfecho, o seu status HTTP e quanto tempo você levou. Os corpos são gravados só em caso de falha — uma troca bem-sucedida guarda tempo e formato e nenhum dos dois corpos, nem o que encaminhamos nem o que você respondeu — e o registro inteiro expira em 24 horas.
“Não está neste registro” não é “não foi guardado”. O que o cliente digitou numa tela data_exchange é guardado e publicado: é o data.submitted da submissão, com cada entrada carregando a tela em que foi preenchida e o momento em que aconteceu, no evento flow.response_received e no GET /v1/flows/{id}/responses, por 31 dias.Um dia a mais que a própria resposta, de propósito. A troca é capturada enquanto a pessoa ainda está preenchendo, minutos antes da submissão que cria a linha da resposta — com uma janela igual, as trocas expirariam primeiro e os últimos minutos de uma resposta publicariam uma lista vazia. O formato completo está em Receber as respostas de um Flow.
O submitted é legitimamente mais curto que esta lista, e isso não é falha. Só as trocas data_exchange são capturadas. O INIT carrega o estado que nós semeamos — os params com que o Flow foi enviado, devolvidos de volta — e o BACK é navegação; nenhum dos dois é uma resposta, então nenhum é guardado. Conte as linhas daqui e as entradas de lá: os dois números não batem.Ele também só acrescenta, nunca reescreve e nunca deduplica: quem volta uma tela e a envia de novo produz uma segunda entrada, genuína, e é o horário que separa as duas passagens sob um mesmo flow_token. E é a requisição que a Meta manda para nós — nunca o que o seu endpoint respondeu, e é por isso que extension_message_response continua não aparecendo em lado nenhum da Pilot Status.

Como configurar, na ordem

Três coisas precisam ser verdade antes de a Meta chamar a sua API, e elas se tornam verdade nesta ordem. Pule uma e você fica com um Flow que parece configurado, não levanta erro em lugar nenhum e nunca é chamado.
1

1. A chave — do NÚMERO

Gere uma conosco, ou importe a que você já usa. A Meta guarda exatamente uma chave pública por telefone, então isso é uma propriedade do número e nunca de um Flow.
2

2. A URL de encaminhamento — do FLOW

Para onde damos POST na troca já descriptografada. Um número serve vários formulários, e cada formulário costuma ser um serviço diferente do seu lado, então isso é por Flow.
3

3. endpoint_uri — na Meta, no Flow

A Meta só chama um endpoint que ela tem registrado, e salvar a URL no passo 2 não registra nada na Meta. O registro é uma requisição própria — POST /v1/flows/{flowId}/endpoint — porque fazê-lo como efeito colateral de um rename sequestraria o Flow de quem já roda um endpoint próprio. Enquanto não for feito, a Meta se recusa a publicar o Flow.
A chave é deste NÚMERO, mesmo aparecendo ao lado de um Flow. Trocá-la muda a descriptografia de todos os Flows data_exchange daquele telefone, não só do que você estava olhando.
Um destino salvo num número sem chave é um Flow que nunca será chamado. A Meta não tem com o que cifrar, então não chega até nós, então o destino nunca é usado — e nada, de nenhum dos lados, reporta isso. É para isso que existem numberHasKey e a lista warnings; FLOW_ENDPOINT_NUMBER_HAS_NO_KEY é o que importa.
Todos esses endpoints exigem flows:manage — inclusive as leituras. endpointUri e metaEndpointUri carregam o token de endpoint do número, e esse token é a única coisa que o endpoint público autentica, enquanto a chave pública correspondente é publicada pela própria Meta. Quem tiver os dois consegue forjar uma requisição que nós vamos descriptografar e encaminhar ao seu webhook sob a NOSSA assinatura. É também por isso que o valor está deliberadamente ausente do GET /v1/flows: uma credencial somente-leitura continua sendo uma credencial.

Passo 1 — a chave: gerar, ou importar a sua

Criamos um par RSA-2048, guardamos a metade privada cifrada e registramos a metade pública na Meta para aquele número.
Gerar SUBSTITUI a chave que a Meta tem para aquele telefone. A Meta guarda uma chave pública por número — registrar a nossa sobrescreve o que estiver lá.Se você já roda um endpoint data_exchange próprio nesse número, ele para de descriptografar no instante em que registramos. É por isso que substituir uma chave viva exige {"confirm": true} e, sem isso, é recusado com FLOW_ENDPOINT_KEY_REQUIRES_CONFIRMATION.A configuração inicial não pergunta, e repetir a chamada para um par que a Meta nunca aceitou (uploadedAt: null) também não — uma repetição reenvia a mesma metade pública já guardada em vez de criar outra, então não há nada a destruir em nenhum dos dois casos.
Leia o estado quando quiser — o que temos, quando a Meta confirmou pela última vez, e se a cópia dela ainda concorda com a nossa:
UNKNOWN não é NOT_SET. Uma chamada ao Graph que falhou não diz nada sobre o que a Meta tem. Ler isso como “sem chave” leva a rotacionar — e rotacionar substitui uma chave que provavelmente estava boa.
endpointUrl para antes do id do Flow: o segmento do token identifica o número, e o segmento seguinte identifica o Flow. O endereço completo que a Meta precisa chamar é {endpointUrl}/{metaFlowId} — que é exatamente o que endpointUri, no recurso do endpoint, já entrega pronto.

Passo 2 — a URL de encaminhamento, no Flow

secret volta em exatamente uma resposta na vida de um segredo: a requisição que o criou. Ele é guardado cifrado e não há caminho de leitura de volta. Copie agora — secret: null numa gravação posterior quer dizer “já existia um e foi mantido”, não “não existe nenhum”. Quem responde isso é hasSecret.Perdeu? {"url": "...", "rotateSecret": true} cria um novo, que é também a única saída para um segredo que vazou.
A URL precisa ser https:// — recusada de outro jeito, e não existe flag para permitir http://. O corpo que encaminhamos são as respostas do formulário do seu usuário final, descriptografadas por nós um salto antes; em http:// isso é dado pessoal em texto puro, colocado ali pelo único participante que já tinha removido a criptografia.{"url": null} limpa o destino e mantém o segredo, então re-apontar o Flow depois não obriga você a redeployar um verificador por causa de um valor que nunca vazou.
PUT e PATCH são a mesma operação aqui — o recurso tem um único campo gravável, então não há sobre o que “substituir” e “mesclar” discordarem. Dê GET no mesmo caminho para ler o estado sem escrever.O número não é parâmetro: ele vem da chave de API (uma chave por número nomeia o dela; uma chave de conta estreita com x-whatsapp-number-id). Nomeá-lo no corpo é recusado com FLOW_NUMBER_FROM_KEY, e uma chave vinculada a um número fora da WABA do Flow recebe 422 FLOW_NUMBER_WABA_MISMATCH em vez de uma resposta sobre um número que nunca poderia enviar esse Flow.

Passo 3 — registre a gente na Meta

A Meta só chama um endpoint que ela tem registrado no Flow. O passo 2 não registrou nada — esta é a requisição que registra:
Ela escreve endpointUri — a nossa URL de proxy para este Flow — no endpoint_uri do Flow na Meta, e responde com o mesmo estado que o GET devolve:
O corpo é opcional, e confirmOverwrite (booleano) é o único campo aceito — o número vem da chave, como em todo o /v1. Não há secret nesta resposta: o POST nunca cria nenhum.
O painel faz o mesmo ato. Na tela de Flows, em Apontar a Meta para cá, o botão Registrar na Meta chama esta rota; um 409 abre uma confirmação mostrando o endereço que você está prestes a deslocar, e confirmOverwrite é o que o botão Assumir mesmo assim envia.
Sem esta chamada, um Flow data_exchange não pode nem ser publicado. A Meta recusa o publish com “Publishing without specifying ‘endpoint_uri’ is forbidden”, e nada mais no Pilot Status escreve esse campo.
Por que é uma segunda requisição e não uma flag na gravação. O PUT é você descrevendo o seu webhook; o POST é a gente dizendo à Meta para rotear o Flow por nós. Fundidos num ato só, um cliente que roda o próprio endpoint de Flow perderia todas as trocas na próxima vez que corrigisse um erro de digitação numa URL — o rename re-apontaria o Flow em silêncio. Eles são separados para que re-apontar seja algo que você faz de propósito.
É idempotente. Um Flow cujo endpoint_uri já é o nosso responde 200 e não escreve nada na Meta. A verificação lê a Meta ao vivo, não a coluna metaEndpointUri sincronizada de hora em hora — essa coluna é muda sobre uma URI registrada na última hora, que é exatamente a janela em que duas pessoas configuram o mesmo Flow.
409 FLOW_ENDPOINT_URI_WOULD_OVERWRITE — a Meta já aponta este Flow para outro lugar. A resposta nomeia esse outro lugar, e só ele:
Reenviar a mesma requisição com {"confirmOverwrite": true} assume o Flow — e assumir tira TODAS as trocas deste Flow de quem responde por ele hoje. A escrita da Meta é uma sobrescrita simples: sem mesclagem, sem desfazer, e nada avisa o endpoint deslocado de que ele parou de ser chamado. Descubra o que está do outro lado de currentEndpointUri antes.A nossa URL deliberadamente não é ecoada de volta: ela carrega o token de endpoint do número, e esse token é a credencial do proxy. currentEndpointUri é o deles, que é o que você precisa para reconhecê-los.
422 FLOW_ENDPOINT_NUMBER_HAS_NO_KEY — o número não tem chave de endpoint de Flow, então não existe URL para registrar. Gere a chave (passo 1) e registre de novo.
Leia o endpoint de volta quando quiser:
Trate a lista como aberta, não fechada: mantenha na tela qualquer código que você não reconheça. Um aviso que você não sabe soletrar continua sendo um aviso, e descartá-lo é como uma tela passa a reportar “está tudo bem” sobre um servidor que acabou de dizer o contrário.

Códigos de erro

Toda falha destes endpoints responde no mesmo envelope — um formato só, uma grafia só:
FLOW_ENDPOINT_URI_WOULD_OVERWRITE é a única falha daqui que acrescenta uma quarta chave, currentEndpointUri. A chave (/v1/numbers/{numberId}/flow-endpoint-key): A URL de encaminhamento (/v1/flows/{flowId}/endpoint):
FLOW_ENDPOINT_NUMBER_HAS_NO_KEY é a única grafia que aparece duas vezes — como item de warnings numa leitura ou gravação, e como 422 no registro. É o mesmo fato em dois papéis: salvar um destino num número sem chave pode dar certo com aviso, e registrar na Meta não pode dar certo de jeito nenhum.
Comuns aos dois: FLOW_BODY_INVALID (400, corpo malformado — um corpo truncado nunca é tratado como vazio), FLOW_UNKNOWN_FIELDS (400), FLOW_NUMBER_FROM_KEY (400, o corpo nomeou o número), FLOW_REQUIRES_META_NUMBER (422), INTERNAL_ERROR (500).
Nunca assuma que o conjunto é fechado — trate os códigos que você conhece e caia no code para o resto.

Exemplo mínimo

Node + Express. Uma tela que responde com os horários de um dia escolhido.
Nunca devolva version. É a versão de protocolo da Meta, ecoada da requisição dela — uma versão errada derruba a troca inteira, não um campo só.

Relacionados