What is a Flow response message?
A Flow response message is the inbound message WhatsApp delivers to your webhook when a customer completes a WhatsApp Flow by tapping the final button on a terminal screen. It arrives through the same messages webhook as every other customer message, as type: "interactive" with interactive.type set to nfm_reply. In the chat, the customer sees their submission as a sent reply under the Flow message.
The nfm_reply object has three fields: name (always flow), body (always Sent) and response_json — a string holding a JSON object with the flow_token you set when sending the Flow plus every field of the terminal screen's complete payload, or, for Flows with an endpoint, the params your endpoint returned in extension_message_response on the SUCCESS screen. Because it is a string, it must be parsed with JSON.parse or json.loads. The message also carries a context object whose id is the wamid of the Flow message being answered.
The response does not say which Flow it came from, which is why the flow_token matters: generate a unique, unguessable token for every send, store it with the customer, the Flow and the purpose, and look it up when the response arrives. A Flow response message is the final submitted output, unlike the data_exchange requests a Flow sends to your Flow endpoint while it is still open, which are encrypted, synchronous and never reach the webhook. Like any message from the customer, it also opens a fresh 24-hour customer service window for your reply.
Key takeaways
- When a customer finishes a Flow, the answers reach your messages webhook as an
interactivemessage of typenfm_reply. nfm_reply.response_jsonis a JSON string, not an object: parse it before reading any field.- The payload has no Flow ID, so the
flow_tokeninsideresponse_jsonis how you tie it to a customer and a Flow. context.idis the wamid of the Flow message the customer answered — useful for deduplication and completion rates.- It carries the final output only; mid-Flow
data_exchangecalls go to your Flow endpoint, never to the webhook.
Figure 1
Open a Flow response and find the answers
{"object": "whatsapp_business_account","entry": [{"id": "102290129340398","changes": [{"field": "messages","value": {"messaging_product": "whatsapp","metadata": {"display_phone_number": "917940001234","phone_number_id": "106540352242922"},"contacts": [{"profile": {"name": "Kiran"},"wa_id": "919898012345"}],"messages": [{"context": {"from": "917940001234","id": "wamid.HBgMOTE5ODk4MDEyMzQ1FQIAERgSQzQ3RTg0QUI"},"from": "919898012345","id": "wamid.HBgMOTE5ODk4MDEyMzQ1FQIAEhgUM0FCRDc5MkQ","timestamp": "1760859000","type": "interactive","interactive": {"type": "nfm_reply","nfm_reply": {"name": "flow","body": "Sent","response_json": "{\"flow_token\":\"hl-2610-ctwa-8f41c2\",\"loan_amount\":\"4500000\",\"city\":\"ahmedabad\",\"employment\":\"salaried\",\"income_band\":\"1l_2l\"}"}}}]}}]}]}
Field 8: entry.0.changes.0.value.messages.0.interactive.nfm_reply.response_json — A JSON string: parse it to get the flow_token and every field of the completion payload.
Example: Home-loan leads traced to their ads
An Ahmedabad housing-finance company runs Click-to-WhatsApp ads that open a lead Flow asking for loan amount, city, employment type and monthly income band. Each send uses a flow_token such as hl-2610-ctwa-8f41c2, stored with the ad that started the chat. When the nfm_reply arrives, the handler parses response_json, finds the token, ignores a second copy of the same wamid delivered by a webhook retry, writes the lead to the CRM tagged with its ad, and replies with a document checklist inside the open window. Leads per ad come straight from the token, with no guessing about which Flow or campaign produced them, and abandoned Flows show up as sends with no matching response.
How does a Flow response message work?
Send the Flow with a meaningful token
Set
flow_tokento a unique value per send — for example a random ID stored against the customer, the Flow and the campaign. It comes back insideresponse_json.The customer completes the Flow
The terminal screen's
completeaction, or your endpoint'sSUCCESSreply, ends the Flow, and the payload is sent as the customer's reply.Detect nfm_reply in the webhook
For each item in
messages, checktype === "interactive"andinteractive.type === "nfm_reply". Button and list replies arrive as otherinteractive.typevalues.Parse, look up and validate
Parse
response_json, find the token in your store, reject unknown or already-used tokens, and validate every field server-side — the values came from a client device.Store once and confirm
Deduplicate on the message
id, save the record, acknowledge the webhook quickly and send a confirmation in the chat while the customer service window is open.
A Flow response message in code
// Called for each item in value.messages after the HTTP handler has verified// X-Hub-Signature-256, returned 200 and put the payload on a queue.export async function handleMessage(msg) {if (msg.type !== "interactive" || msg.interactive?.type !== "nfm_reply") return;if (await processed.has(msg.id)) return; // webhook retries resend the same wamidlet answers;try {answers = JSON.parse(msg.interactive.nfm_reply.response_json); // a string, not an object} catch {return logBadPayload(msg.id);}const send = await flowTokens.get(answers.flow_token); // the payload has no flow_idif (!send || send.usedAt) return; // unknown or already submittedconst { flow_token, ...fields } = answers;await leads.create({...validateLead(fields), // server-side checks on every valuewaId: msg.from,flowId: send.flowId,campaign: send.campaign,flowMessageId: msg.context?.id, // the Flow message this answers});await flowTokens.markUsed(flow_token);await processed.add(msg.id);await sendText(msg.from, "Thanks! Your details are in. An advisor will message you within 2 working hours.");}
Filter, deduplicate, parse, look up the token, validate, store, confirm.
Common mistakes with a Flow response message
Treating response_json as an object
It is a string, so
response_json.flow_tokenis undefined. Parse it first, inside a try/catch, and log payloads that fail to parse.Expecting a Flow ID
There isn't one. If several Flows share a handler and your tokens are neither unique nor stored, you cannot tell their responses apart. Store the Flow and purpose against every token.
Processing webhook retries twice
Webhooks are retried, so the same
nfm_replycan arrive more than once. Deduplicate on the messageidand mark tokens as used once a submission is stored.Trusting the submitted fields
Values come from the customer's device and are only as constrained as the Flow's components. Validate types, lengths and formats on the server before writing to a CRM.
Listening in the wrong place
The final submission never goes to the Flow endpoint, and mid-Flow
data_exchangecalls never go to the webhook. Wire each to its own handler.
Best practices for a Flow response message
- Keep the
completepayload small: only the fields you will actually store. - Confirm in the chat with a summary of the key answers; customers trust what they can see.
- Store
context.idwith each response to measure completion per Flow send. - Mask personal fields in logs — Flow responses often contain phone numbers, addresses and income details.
Why it matters
Why does a Flow response message matter?
This message is where a Flow pays off — the lead, booking or survey answer your systems actually keep. Careless handling loses data quietly: code that expects an object finds a string, code that keys on a Flow ID finds none, and retried webhooks create duplicate bookings. A small, well-tested handler that parses, looks up the token, validates, deduplicates and confirms in the chat turns Flow completions into reliable records.
Flow Response Message FAQs
- How do I get the data a customer entered in a WhatsApp Flow?
- Read it from the
nfm_replymessage your webhook receives when they finish. Parseinteractive.nfm_reply.response_json, which is a JSON string, to get theflow_tokenand every field the terminal screen sent with itscompleteaction. - Why doesn't the Flow response include the flow_id?
- Meta's payload simply doesn't carry it; the
flow_tokenis the correlation key. Generate a unique token for every send and store which Flow, customer and campaign it belongs to, then look it up when the response arrives. - Is a Flow response message the same as a data_exchange request?
- No.
data_exchangerequests are encrypted calls to your Flow endpoint while the Flow is open; the Flow response message is a normal webhook message sent once, when the customer completes the Flow. - Do I get a response if the customer closes the Flow without finishing?
- No. A Flow response message is sent only on completion, so an abandoned Flow produces nothing at the webhook. Measure drop-off by comparing Flow sends with the responses that come back.