Skip to content
The InfiQ GlossaryFeature

Bots, flows & AI

Flow Response Message

A Flow response message is the inbound nfm_reply your webhook receives when a customer completes a WhatsApp Flow, carrying response_json and the flow_token.

Also called: nfm_reply · Flow completion message · Flow response webhook

InfiQ Glossaryreviewed Practitioner

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 interactive message of type nfm_reply.
  • nfm_reply.response_json is a JSON string, not an object: parse it before reading any field.
  • The payload has no Flow ID, so the flow_token inside response_json is how you tie it to a customer and a Flow.
  • context.id is the wamid of the Flow message the customer answered — useful for deduplication and completion rates.
  • It carries the final output only; mid-Flow data_exchange calls go to your Flow endpoint, never to the webhook.

Figure 1

Open a Flow response and find the answers

8 / 8
{
"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\"}"
}
}
}
]
}
}
]
}
]
}
A real-shaped nfm_reply webhook. Step through the fields a handler reads: the reply type, the Flow message it answers and the response_json string that holds the flow_token and the customer's answers.

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.

— Home-loan leads traced to their ads

How does a Flow response message work?

  1. Send the Flow with a meaningful token

    Set flow_token to a unique value per send — for example a random ID stored against the customer, the Flow and the campaign. It comes back inside response_json.

  2. The customer completes the Flow

    The terminal screen's complete action, or your endpoint's SUCCESS reply, ends the Flow, and the payload is sent as the customer's reply.

  3. Detect nfm_reply in the webhook

    For each item in messages, check type === "interactive" and interactive.type === "nfm_reply". Button and list replies arrive as other interactive.type values.

  4. 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.

  5. 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 wamid
let 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_id
if (!send || send.usedAt) return; // unknown or already submitted
const { flow_token, ...fields } = answers;
await leads.create({
...validateLead(fields), // server-side checks on every value
waId: 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_token is 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_reply can arrive more than once. Deduplicate on the message id and 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_exchange calls never go to the webhook. Wire each to its own handler.

Best practices for a Flow response message

  • Keep the complete payload 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.id with 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_reply message your webhook receives when they finish. Parse interactive.nfm_reply.response_json, which is a JSON string, to get the flow_token and every field the terminal screen sent with its complete action.
Why doesn't the Flow response include the flow_id?
Meta's payload simply doesn't carry it; the flow_token is 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_exchange requests 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.

Try it on the official WhatsApp Business API

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.