GLOSSARY

What is a WhatsApp Flow?

A WhatsApp Flow is a multi-screen, form-like experience that opens inside the WhatsApp chat, defined by a declarative Flow JSON document of screens and components. It either runs entirely on its own content or exchanges encrypted requests with an endpoint you host, and the completed submission comes back to your webhook as an nfm_reply.

Free forever plan · No credit card required · Cancel anytime

Quick definition

A WhatsApp Flow is a multi-screen, form-like experience that opens inside the WhatsApp chat, defined by a declarative Flow JSON document of screens and components. It either runs entirely on its own content or exchanges encrypted requests with an endpoint you host, and the completed submission comes back to your webhook as an nfm_reply.

In a single sentence: a form that never leaves WhatsApp, so the customer never leaves either.

What it means

A WhatsApp Flow is the platform's answer to a problem every conversational business runs into: some things are not conversations. Booking an appointment needs a date, a time, a service and a name. Asking those four questions one message at a time is slow, easy to abandon and miserable to parse. A Flow puts them on one or more screens inside WhatsApp, validates the input, and hands you a single structured submission.

The alternative before Flows was a link to a web form. That works technically and fails commercially, because the tap out of WhatsApp into a browser is where a large share of people stop. The Flow keeps the whole interaction inside the app the customer already trusts, with no page load, no cookie banner and no login.

A Flow is defined by Flow JSON, a declarative document with a version and a list of screens, each screen holding a layout of components: headings, body text, text inputs, dropdowns, radio groups, checkboxes, date pickers, opt-in controls and a footer that moves the user forward. The component set is versioned, and Meta introduces new components behind version bumps, so a Flow that copies an example from an older article may reference a component your declared version does not have.

The two modes, and choosing the right one

Everything about the operational risk of a Flow follows from which mode you pick.

Navigate mode is static. The Flow JSON contains every screen and every option, WhatsApp renders it, and you receive the answers at the end. There is no server involved during the session. If your form has fixed questions, this is the mode you want, and a surprising number of real use cases fit: feedback surveys, lead qualification, contact capture, complaint intake, event registration.

Data exchange mode is dynamic. Each screen transition calls an endpoint you host, which returns the data for the next screen. This is what you need for real appointment slots, live stock, a customer's own order list, or branching that depends on an earlier answer. The price is that your endpoint is now part of the user interface, with everything that implies for latency and uptime.

Our advice is blunt: start in navigate mode and only move to data exchange when a screen genuinely cannot be known in advance. Half the dynamic Flows we see could have been static with one extra screen.

The endpoint contract

A data exchange Flow calls your endpoint with an encrypted body. You generate an RSA key pair, upload the public key against your phone number, and Meta encrypts each request with a symmetric key that is itself wrapped with your public key. Your endpoint decrypts the request, decides what the next screen contains, and returns a response encrypted with the same material.

Three things about this contract cause most production incidents:

  • The health check is real traffic. Meta sends a ping action to verify the endpoint. If you return the wrong shape for it, the Flow cannot be published, and the error message rarely says so plainly.
  • Latency is user-visible. The customer is looking at a spinner mid-form. Anything you cannot answer quickly should not be called synchronously here.
  • Failures are punished. Sustained endpoint errors move the Flow into a throttled state that limits how often it can be opened, and continued failure can block it. A deploy that breaks the endpoint for an hour can leave the Flow degraded afterwards.

Because of that last point, treat a Flow endpoint like a payment callback rather than like an internal API. Version it, monitor it, and never let a slow third-party call sit inside the request path.

Delivery and the window

A Flow reaches the customer in one of two ways, and the choice is governed by the 24-hour customer service window.

Inside an open window you send it as an interactive message of type flow. No approval, no review queue, deploy whenever you like. Outside the window you need an approved template with a Flow button, which Meta reviews like any other template.

The practical pattern that works well: a small number of approved templates whose only job is to open a Flow from cold, and a larger, faster-moving set of Flows sent free-form once the customer has engaged.

Getting the answer back

When the customer finishes, your webhook receives a message whose interactive type is nfm_reply. It carries a response_json string with the submitted values and, crucially, the flow_token you set when you sent the Flow.

The token is your correlation key. Set it to an opaque identifier that resolves, on your side, to the contact, the conversation and the reason the Flow was sent. Two rules follow: never put personal data in it, since it is not a secure channel for that, and never rely on matching by phone number alone, since the same person may have two Flows open for two different reasons.

One more detail that surprises people: response_json arrives as a string, not an object, so it needs parsing, and it should be parsed defensively. A Flow whose screens changed since the session started can return a shape your handler was not written for.

Why it matters

Flows are where WhatsApp stops being a chat channel and starts being an application surface. Completion rates on an in-chat form are materially better than on a linked web form, for the simple reason that nobody has to leave, wait for a page, or accept cookies. For any business whose conversion depends on structured input, booking, ordering, returns, onboarding, that difference is the entire business case.

There is a data quality argument too. Free-text answers arrive misspelled, in the wrong format and in the wrong order. A Flow gives you validated fields with known keys, which is the difference between a CRM record you can act on and a transcript somebody has to read.

Real-world examples

  1. Clinic booking. A reminder template with a Flow button. The Flow reads real availability from the practice management system in data exchange mode and writes the booking back on submission. No phone call, no web form.
  2. Returns intake. Navigate mode, four fields: order number, item, reason from a fixed list, free-text comment. No endpoint, no uptime risk, and the submission opens a ticket automatically.
  3. Lead qualification. Sent free-form the moment an ad click opens a conversation, while the free entry point window is still running. Budget, timeline and use case captured in one screen instead of six messages.
  4. The throttled Flow. A team deployed a change that made their endpoint return 500 for forty minutes. The Flow was throttled, appointment bookings failed for the rest of the day, and the cause was invisible from the messaging dashboard.

Common mistakes

  • Choosing data exchange by default. If the screens are fixed, static mode removes an entire class of production risk.
  • Calling a slow API inside the endpoint. The customer is watching. Cache it or precompute it.
  • Not handling the health check. Publishing fails and the reason is not obvious.
  • Using the flow token as storage. It is a correlation key, not a place for customer data.
  • Assuming response_json is an object. It is a string, and it should be parsed defensively.
  • Building a ten-screen Flow. Abandonment compounds per screen. Ask for what you need to act, and get the rest in conversation.
  • Forgetting the customer can just stop. An abandoned Flow sends nothing at all, so plan a follow-up rather than waiting for a submission that never arrives.

Related concepts

How Pinlyx handles it

Pinlyx sends Flows as part of a conversation or a sequence, maps every submitted field onto CRM fields so the answer becomes structured contact data rather than a JSON blob on a timeline, and correlates completions through the flow token to the contact and the campaign that triggered them. Abandonment is visible, which means an unfinished booking can trigger a follow-up instead of disappearing. Where a Flow needs live data, the endpoint sits behind the same webhook infrastructure that serves the rest of the platform, with latency monitored because a slow endpoint is a customer-facing failure. See WhatsApp CRM.

Decision table · static or dynamic

One choice decides all of your operational risk.

Pick static unless a screen genuinely cannot be known in advance.

ModeEndpointUse whenWhat can go wrong
navigate (static)NoEvery screen and every option is known when you publish: a feedback survey, a fixed contact form, a lead qualification questionnaire.Almost none. Nothing can time out because nothing is fetched. Content changes require republishing.
data_exchange (dynamic)YesA screen depends on live data or on what was entered before: real appointment slots, stock levels, an address book, branching questions.Your endpoint becomes part of the user experience. Slow or failing responses show the customer an error and can get the Flow throttled.
Flow JSON, the send, and the completion

A static booking Flow, end to end.

Component names and the version string are versioned by Meta. Build against the version you declare.

// Flow JSON: one screen, no endpoint, nothing to keep running
{
  "version": "5.0",
  "screens": [{
    "id": "BOOK",
    "title": "Book a visit",
    "terminal": true,
    "layout": {
      "type": "SingleColumnLayout",
      "children": [
        { "type": "TextHeading", "text": "Pick a slot" },
        { "type": "RadioButtonsGroup", "name": "slot", "label": "Available times",
          "required": true,
          "data-source": [
            { "id": "fri_10", "title": "Friday 10:00" },
            { "id": "fri_14", "title": "Friday 14:00" },
            { "id": "mon_09", "title": "Monday 09:00" }
          ] },
        { "type": "TextInput", "name": "note", "label": "Anything we should know?",
          "required": false },
        { "type": "Footer", "label": "Confirm",
          "on-click-action": { "name": "complete",
            "payload": { "slot": "${form.slot}", "note": "${form.note}" } } }
      ]
    }
  }]
}

# Sending it free-form, inside an open 24-hour window
POST /v22.0/{phone-number-id}/messages
{
  "messaging_product": "whatsapp", "to": "905551112233",
  "type": "interactive",
  "interactive": {
    "type": "flow",
    "body": { "text": "Tap below to book your visit." },
    "action": {
      "name": "flow",
      "parameters": {
        "flow_message_version": "3",
        "flow_id": "1234567890",
        "flow_cta": "Book now",
        "flow_token": "bk_9f2c31e0",     // opaque, resolves to contact + reason
        "mode": "published"
      }
    }
  }
}

# The completion, on your webhook. response_json is a STRING.
{
  "type": "interactive",
  "interactive": {
    "type": "nfm_reply",
    "nfm_reply": {
      "name": "flow",
      "body": "Sent",
      "response_json": "{\"slot\":\"fri_14\",\"note\":\"Wheelchair access\",\"flow_token\":\"bk_9f2c31e0\"}"
    }
  }
}
Shipping checklist

Seven checks before a Flow goes live.

  • Default to navigate mode. Only go dynamic when a screen truly depends on live data.
  • Handle the health check action correctly, or publishing will fail with an unhelpful error.
  • Keep the endpoint fast. Never call a slow third-party service inside the request path.
  • Set an opaque flow_token that resolves to a contact, a conversation and a reason.
  • Parse response_json defensively. It is a string and its shape can change under you.
  • Keep the Flow short. Abandonment compounds with every additional screen.
  • Plan a follow-up for abandonment, because an unfinished Flow sends you nothing at all.
Watch out for

Your endpoint is now part of the user interface.

A dynamic Flow moves your backend into the middle of a form the customer is filling in. A deploy that adds two seconds of latency does not show up as a slow API, it shows up as a spinner in front of a person who is trying to book an appointment. Sustained errors do worse than fail the session: Meta throttles the Flow, so the damage outlives the incident. If the screens can be static, make them static, and if they cannot, treat that endpoint with the same care as a payment callback.

WhatsApp Flows: FAQ

What teams need to know before putting a form inside the chat.

Buttons and lists are single messages that ask one question and return one answer, and every step lives in the chat transcript. A Flow opens a separate screen inside WhatsApp, can collect several fields at once, can validate input, and returns one structured submission at the end. Use interactive messages for a choice, and a Flow when you would otherwise be asking six questions in a row and stitching the answers together yourself.
Only if the Flow needs live data. A navigate-mode Flow is entirely self-contained: the Flow JSON describes every screen and every option, WhatsApp renders it, and you get the answers at the end. That covers a large share of real use cases, including surveys, lead capture and fixed contact forms, with no endpoint, no encryption keys and no uptime obligation. Reach for data exchange mode only when a screen genuinely cannot be known in advance.
You generate an RSA key pair and upload the public key to the business encryption endpoint for your phone number. Meta then encrypts each request to your endpoint with a symmetric key, wraps that key with your public key, and expects the response encrypted with the same symmetric material. Your endpoint decrypts, does its work and re-encrypts. The practical consequences are that key rotation is an operational task with real consequences, and that a plaintext debugging shortcut is not available in production.
The customer sees an error inside a screen they cannot recover from, which is far more damaging than a slow chatbot reply because it happens mid-form after they have already entered data. Meta also monitors endpoint health: sustained errors move a Flow into a throttled state that severely restricts how often it can be opened, and repeated failure can block it entirely. Treat the endpoint as a latency-critical service, respond quickly, and never call a slow third-party API synchronously inside it.
Two ways, and the difference is the 24-hour window. Inside an open customer service window you send it as an interactive message of type flow, with no approval required. To start a conversation from outside the window you need an approved template carrying a Flow button, which means Meta reviews the template first. The pattern that works is a template with a Flow button for the cold start, and free-form Flows for everything that happens after the customer engages.
With the flow_token. You set it when you send the Flow, it travels with the session, and it comes back in the completion payload. Put an opaque identifier in it that you can resolve to a contact, a conversation and the reason you sent the Flow. Never put personal data in it, and never trust the response_json alone to tell you who answered, because the token is the thing that carries your context.
Ready to ship

Forms that never leave the chat.

Pinlyx sends WhatsApp Flows from campaigns and conversations, maps every answer onto CRM fields, and tracks abandonment so a lost booking is recoverable.

Free forever plan · GDPR-ready · No credit card required

We value your privacy

We use cookies to improve our site, analyze traffic, and personalize ads. You can accept all, reject non-essential, or customize your choices. Read our Cookie Policy.