Sent.
Nothing was lost. The answers travel on a different event, and that event has to be subscribed to explicitly.
What Meta actually delivers
A submitted Flow does not arrive as a text message. It arrives as an interactive reply of typenfm_reply, with three fields:
Why the message text says Sent
Because that is the only thing in the payload that is message text. The chat bubble, chat_messages.text, content on message.* events and GET /v1/messages all carry the same value, and that value is Meta’s body (falling back to the Flow name when body is blank).
The answers are deliberately not folded into
content. It is a public contract: an integrator reading content is promised message text. Putting a JSON blob there would break every consumer that renders or matches on it, and would spill names, phone numbers and document numbers into a plain-text field with no schema — irreversibly, once a customer has started parsing it.The answers get a surface of their own instead, where they are structured, correlatable by flow_token, and pruned on a retention window.The answers arrive on flow.response_received
- Dashboard
- API
Open Webhooks (
/webhooks), edit the webhook attached to the Flow’s number, and tick flow.response_received in the event picker. events is replaced wholesale when you save, so keep the events you already had.The payload
Chatwoot shows content: null — that is Chatwoot
On the API-mirror channel, we do add them. When the conversation is mirrored by Pilot Status, a submission is posted twice: the public bubble is replaced with a short fixed line (
📋 Formulário respondido) instead of Meta’s label, and the answers go into a private note on the same conversation — one field: value line per answer, agents only, never shown to the customer.The note is a convenience for humans and is capped per value; the event is the machine-readable copy and is never truncated. See Chatwoot.Reading answers later
The event is the live channel. The stored copy is atGET /v1/flows/{id}/responses, joined to the contact who answered, and kept for 30 days.
The retention window is enforced on read, so a submission past its expiry is never served — pull what you need to keep into your own store rather than treating this endpoint as the archive.
Minimal example
Node + Express. It verifies the signature over the raw body, then reads the answers.Still empty? Check these, in order
1
Is flow.response_received in the webhook's events?
GET /v1/webhooks and read the list back. A webhook with ["messages"] receives inbound messages and nothing else.2
Is the webhook on the right number?
Webhooks are scoped per number, and a Flow is answered on the number that sent it.
3
Is the webhook active, and is the delivery being attempted?
GET /v1/webhooks/{id}/logs shows recent attempts. A paused webhook (active: false) delivers nothing.4
Are you reading content instead of data.response?
content is Meta’s label and always will be. The answers are in data.response.5
Are you reading the delivery LOG rather than the delivery?
On a number configured to keep no customer content, the answers are still delivered to your endpoint in full — it is the stored copy of the payload that is blanked (
from, response and responseRaw nulled). A log entry that looks empty next to a 200 is that redaction, not a failed delivery.Related
- Flows — lifecycle, publishing, cloning
- Connect your API to a Flow — the
data_exchangehalf - Webhook events
- Flows API