Enviado.
Nada se perdeu. As respostas viajam em outro evento, e esse evento precisa ser assinado explicitamente.
O que a Meta entrega de verdade
Um Flow enviado não chega como mensagem de texto. Chega como uma resposta interativa do tiponfm_reply, com três campos:
Por que o texto da mensagem diz Enviado
Porque é a única coisa do payload que é texto de mensagem. A bolha do chat, chat_messages.text, o content dos eventos message.* e o GET /v1/messages carregam todos o mesmo valor, e esse valor é o body da Meta (com o nome do Flow como fallback quando o body vem em branco).
As respostas ficam de fora do
content de propósito. Isso é um contrato público: quem lê content recebeu a promessa de texto de mensagem. Colocar um blob JSON ali quebraria todo consumidor que renderiza ou casa em cima dele, e derramaria nomes, telefones e documentos num campo de texto puro sem schema — de forma irreversível, assim que um cliente começasse a fazer o parse.As respostas ganham uma superfície própria, onde são estruturadas, correlacionáveis pelo flow_token e podadas por uma janela de retenção.As respostas chegam em flow.response_received
- Dashboard
- API
Abra Webhooks (
/webhooks), edite o webhook ligado ao número do Flow e marque flow.response_received no seletor de eventos. O events é substituído inteiro ao salvar, então mantenha os eventos que você já tinha.O payload
Num Flow com endpoint as respostas ficam em outro lugar
Oresponse é o nfm_reply da Meta, e o que esse reply carrega depende de como o Flow foi construído.
Um Flow simples — sem endpoint — carrega tudo: as seleções como id e o texto livre como foi digitado. É o payload acima.
Um Flow data_exchange não. As respostas dele chegaram ao seu endpoint tela por tela, minutos antes, enquanto a pessoa ainda preenchia o formulário; o que a Meta manda no fim é o recibo. Medido três vezes em produção em 6 de setembro de 2026, o response era exatamente isto, inteiro:
{ "observacao": "Aobaaa", "foto": [ … ] }. Nada falhou no parse e nada foi descartado: num Flow data_exchange, o response é o lugar vazio.
Por isso as trocas em si são publicadas — como data.submitted neste evento, e como submitted no GET /v1/flows/{id}/responses, no mesmo formato nos dois:
Uma entrada tem uma de duas formas e nunca uma terceira:
data preenchido com omitted: null, ou data: null com o motivo de não estar lá.
O
submitted é null — nunca [], pelo mesmo motivo do flowMedia. A cópia guardada de um campo só é trocada por um marcador quando ele tem algo dentro, então um array vazio voltaria parecendo “houve respostas e nós escondemos”. null diz que não há nada para mostrar.Ele é append-only e nunca deduplicado. Quem volta uma tela e envia de novo produz uma segunda entrada, e essa entrada é legítima — foi o que a pessoa fez. Duas passagens sob o mesmo flow_token se distinguem pelo at, não pela ausência de repetição.As trocas são guardadas por 31 dias — um dia a mais do que a resposta a que pertencem, e o dia extra é de propósito. Uma troca é capturada enquanto a pessoa ainda preenche o formulário, minutos antes da submissão que cria a linha da resposta. Com 30 dias iguais, as trocas venceriam primeiro, e uma resposta servida nos últimos minutos dela publicaria um
submitted vazio ao lado de tudo o que ela ainda tivesse.Uma opção escolhida chega como o id, nunca como o rótulo
Um componente de seleção — RadioButtonsGroup, e igualmente Dropdown e ChipsSelector — declara as opções no Flow JSON num data-source, em que cada opção é um id acompanhado de um title:
title. O que a Meta envia é o id. Quem tocou em Otimo produz exatamente isto:
nota é "5"; a palavra Otimo não viaja. Os componentes de texto livre (TextInput, TextArea) carregam sim o que foi digitado — o comentario acima — então uma mesma submissão mistura os dois tipos, e só as seleções ficam opacas. Isso é um Flow simples. Com um endpoint data_exchange nada disso está no response — o que foi digitado e o que foi escolhido viajaram os dois até o seu endpoint, e o que capturamos deles está no submitted.
A tela “Sua resposta” / “Your response” do próprio WhatsApp mostra
Otimo, e isso não é contradição. O aparelho ainda tem o Flow JSON, então resolve o rótulo para quem respondeu. O seu endpoint recebe só a submissão: 5, sem nada no payload dizendo o que 5 significava. O mapa id → title existe somente no Flow JSON, então guarde a sua própria cópia do data-source se você precisa das palavras.O flow_token que aparece no meio das respostas é o eco do token pela Meta, não uma resposta — o mesmo valor já chega em data.flowToken.Arquivos anexados dentro do formulário
UmPhotoPicker ou um DocumentPicker coloca um arquivo na submissão, e ele não chega em response — os bytes vão para a Meta, e a referência que chega até eles viaja dentro do pedido data_exchange cifrado que só o proxy decifra, nunca no nfm_reply que vira o response. Os anexos vêm num array próprio, flowMedia:
O
url vem null em três casos, e “ainda não transferiu” não é nenhum deles. A instalação não tem segredo de assinatura configurado — aí ela cai no comportamento antigo, a URL direta do objeto assim que o status for STORED, nunca nada; a Meta não mandou media_id para assinarmos uma identidade em cima; ou o número é RELAY_ONLY, e nenhum anexo chegou a ser guardado.flowMedia é null — nunca [] — quando não há nada listado, e os dois não são intercambiáveis. Num número que não guarda conteúdo do cliente, a cópia guardada de um campo só é trocada por um marcador quando ele tem algo dentro; um array vazio voltaria parecendo “houve anexos e nós escondemos”.A superfície de leitura faz a escolha oposta: media, no GET /v1/flows/{id}/responses, é sempre um array, vazio quando não houve nada. Ela também traz sizeBytes, que o evento não traz.Nada de
cdn_url, nada de encryption_metadata, em superfície nenhuma. A Meta deriva uma chave AES por arquivo e a manda ao lado da referência. Repassar qualquer um dos dois colocaria a chave que abre o documento dentro do seu webhook e dos logs dele, então nenhum é guardado e nenhum é publicado.O Chatwoot mostra content: null — isso é do Chatwoot
No canal espelhado por API, nós acrescentamos. Quando a conversa é espelhada pelo Pilot Status, uma submissão é publicada duas vezes: a bolha pública é substituída por uma linha fixa curta (
📋 Formulário respondido) no lugar do rótulo da Meta, e as respostas vão para uma nota privada na mesma conversa — uma linha campo: valor por resposta, só para atendentes, nunca visível para o cliente.A nota é uma conveniência para humanos e tem limite por valor; o evento é a cópia legível por máquina e nunca é truncado. Veja Chatwoot.Ler as respostas depois
O evento é o canal ao vivo. A cópia armazenada está emGET /v1/flows/{id}/responses, unida ao contato que respondeu, e guardada por 30 dias — com as trocas do submitted ao lado dela guardadas por 31, para que uma resposta nunca seja servida sem as respostas de que ela foi feita.
A janela de retenção é aplicada na leitura, então uma submissão vencida nunca é servida — puxe para a sua própria base o que você precisa guardar, em vez de tratar este endpoint como arquivo.
Exemplo mínimo
Node + Express. Valida a assinatura sobre o corpo cru e lê as respostas.Ainda vazio? Confira nesta ordem
1
O flow.response_received está no events do webhook?
Faça
GET /v1/webhooks e leia a lista de volta. Um webhook com ["messages"] recebe mensagens e mais nada.2
O webhook está no número certo?
Webhooks têm escopo por número, e um Flow é respondido no número que o enviou.
3
O webhook está ativo, e a entrega está sendo tentada?
GET /v1/webhooks/{id}/logs mostra as tentativas recentes. Um webhook pausado (active: false) não entrega nada.4
Você está lendo content, ou data.response num Flow com endpoint?
content é o rótulo da Meta e sempre será — ele nunca carrega resposta. E o data.response só as carrega num Flow simples.Se o Flow tem endpoint data_exchange, o response é { "flow_token": "..." } e mais nada; as respostas estão em data.submitted, uma entrada por tela que a pessoa enviou. Um payload com um token e nada além disso não é uma submissão vazia — é o campo errado. Veja Num Flow com endpoint as respostas ficam em outro lugar.5
Você está lendo o LOG da entrega em vez da entrega?
Num número configurado para não guardar conteúdo do cliente, as respostas continuam sendo entregues ao seu endpoint por inteiro — o que é apagado é a cópia guardada do payload (
response, responseRaw, submitted e flowMedia viram null; o from vira a string marcadora conteúdo indisponível, que é string e não null). Um registro que parece vazio ao lado de um 200 é essa ocultação, não uma entrega falhada.Essa ocultação só é guardada numa entrega bem-sucedida. Uma entrega que FALHOU guarda o payload real, para poder ser inspecionada e reenviada; uma varredura substitui o payload dessas linhas — inteiro, por um objeto marcador — quando elas passam de 24 horas. Até lá, a linha falhada tem a submissão.Os anexos e as trocas capturadas são a exceção no sentido contrário: num número desses os arquivos nunca são re-hospedados e as trocas nunca são guardadas, então flowMedia e submitted vêm null também na entrega. Nada se perdeu — nada chegou a ser guardado.Relacionados
- Flows — ciclo de vida, publicação, clonagem
- Conecte a sua API a um Flow — a metade
data_exchange - Eventos de webhook
- API de Flows