Estimated reading time: 8 minutes Updated: 7/14/2026 Created by: Botmaker Team

Dynamic WhatsApp Flows: connect an endpoint and exchange data in real time

In this article you'll learn how to create a dynamic Flow, set up the Client Action that acts as the endpoint, propagate data across screens, and map variables in the Botmaker platform.



A dynamic Flow is a WhatsApp Flow that communicates with an external server (the endpoint) while the user navigates through the screens. Unlike a static Flow—which only collects data and sends it at the end—a dynamic Flow exchanges information in real time, letting you load up-to-date lists, validate data on the server, and personalize content screen by screen.

Note: This article assumes you already know how to create a basic static Flow. The focus here is exclusively on the dynamic part.

You can:

  • Load option lists in real time (products, time slots, cities, etc.).
  • Validate data on the server before moving to the next screen.
  • Personalize content based on the user's profile.
  • Run business logic between screens.
  • Propagate the collected data through to the end of the flow.

Prerequisites

Access to your Botmaker account with permissions to create Client Actions and manage WhatsApp Flows. A WhatsApp Flow already created (even with a single screen) that you'll work on. Basic knowledge of the structure of a Flow JSON.


Key concepts

Dynamic Flow: a WhatsApp Flow that communicates with an endpoint during the user's navigation, instead of sending the data only at the end.

Endpoint: the service that receives the current screen's data, runs the required logic, and returns the next screen along with the data it should display. In Botmaker it's implemented through a Client Action (CA) of type WA Flow Endpoint.

Client Action (CA): a Botmaker code action. For dynamic Flows it must be of type WA Flow Endpoint—no other type works as a Flow backend.

data_exchange: the action triggered when the user clicks the button on a screen configured with it. It sends the current screen's data to the endpoint.

Payload: the JSON object exchanged between the Flow and the endpoint. Botmaker delivers it already decrypted.

Data propagation: the technique of manually forwarding, screen by screen, the data collected on earlier screens so it reaches the end of the flow.


Action types the endpoint receives

INIT: the Flow was opened with flow_action: data_exchange (the first screen is loaded dynamically). You must return the initial data for the first screen. data_exchange: the user clicked the footer button with this action. You must process the submitted data and return the next screen.

BACK: the user returned to a screen with refresh_on_back: true. You must reload the previous screen's data.

ping: Meta's periodic health check. You must always respond with { status: 'active' }.


How to create and configure a dynamic Flow

Step 1: Create the Client Action (CA)

  • Go to the Code menu in the Botmaker platform and click Create new code action.
  • Fill in the fields: Name (for example, flow-scheduling-endpoint), TypeWA Flow Endpoint, and Code templateflow_basic_template to start from the base structure.
  • Click Publish.


Warning: Only CAs of type WA Flow Endpoint are compatible with dynamic Flows. Other types (User, standard Endpoint) don't work as a Flow backend.


Step 2: Copy the service URL

After publishing, open the CA's General tab and copy the service URL. It follows this format:

https://functions.botmaker.com/whatsapp-flows/{BusinessID}/{WabaID}/{caName}

Save this URL—you'll use it as the Endpoint URI in the Flow configuration.


Warning: The Flow only works on the WABA ID it was configured on. Make sure you use the URL for the correct WABA; switching WABAs will cause the Flow to stop working.


Step 3: Link the endpoint to the Flow

  • Go to Chatbots → WhatsApp Flows and open the desired Flow.
  • Go to the General tab and paste the copied URL into the Endpoint URI field.

From that moment on, every Flow screen configured with the data_exchange action will call your CA automatically.


Tip: Each Flow can have only one endpoint. If you have multiple flows with different logic, create separate CAs—or handle all the logic inside a single CA by identifying the screen it receives.


Step 4: Understand what the endpoint receives and must return

When called by the Flow, the endpoint receives a JSON payload that Botmaker has already decrypted:

{

  "version": "3.0",

  "action": "data_exchange",

  "flow_token": "<token>",

  "screen": "CURRENT_SCREEN_NAME",

  "data": {

    "submitted_field": "value entered by the user"

  }

}

The endpoint must always return a JSON with the next screen and the data it needs to render:

{

  "screen": "NEXT_SCREEN",

  "data": {

    "next_screen_variable": "value"

  }

}


For dynamic lists used in components like RadioButtonsGroup, CheckboxGroup, or Dropdown, the array must follow this format:

{

  "screen": "SELECTION",

  "data": {

    "options_list": [

      { "id": "1", "title": "Option A" },

      { "id": "2", "title": "Option B" }

    ]

  }

}


Note: Only CheckboxGroup, RadioButtonsGroup, and Dropdown support dynamic lists. The data type defined in the Flow JSON must exactly match what the CA returns.


Step 5: Write the Client Action logic

The CA receives the global variables screen and data. Use them to identify which screen the user is on and what they submitted. Here's the base structure:

// Handle the ping (required health check)

if (!screen || data.action === 'ping') {

  flow.data = { status: 'active' };

  flow.send();

  return;

}


// First screen: return a dynamic list

if (screen === 'SELECTION') {

  flow.data = {

    products_list: [

      { id: 'p1', title: 'Product A' },

      { id: 'p2', title: 'Product B' },

    ],

  };

  flow.nextScreen = 'CONFIRMATION';

  flow.send();


// Second screen: process the selection

} else if (screen === 'CONFIRMATION') {

  flow.data = {

    summary: `You chose: ${data.selected_product}`,

  };

  flow.nextScreen = 'RESULT';

  flow.send();

}


Warning: Always call flow.send() at the end of each block. If you forget, the Flow will hang waiting for the endpoint's response.

Important: The ping must be handled without exception. If the endpoint doesn't respond correctly to Meta's health check, the Flow can be flagged as unavailable and stop working for users.

When a screen has the property refresh_on_back: true in the Flow JSON, going back to it calls the endpoint with action: BACK. Use it to reload data or clear previous selections:

if (data.action === 'BACK' && screen === 'SELECTION') {

  flow.data = {

    products_list: fetchUpdatedProducts(),

  };

  flow.nextScreen = 'SELECTION';

  flow.send();

}


Step 6: Reference the data on the screens (Flow JSON)

The data returned by the endpoint becomes available on the screens with the syntax ${data.field_name}. Data the user fills in a form is accessed with ${form.field_name}.

To display a value from the endpoint:

{

  "type": "TextSubheading",

  "text": "${data.summary}"

}

To send a form value to the endpoint:

{

  "on-click-action": {

    "name": "data_exchange",

    "payload": {

      "selected_product": "${form.selected_product}"

    }

  }

}


Step 7: Propagate data across screens

Meta sends Botmaker only the variables present in the last screen's payload—not those from every screen automatically. For data collected on earlier screens to reach the end, you have to propagate it manually: when navigating from one screen to another, include in the on-click-action payload both the fields filled on the current screen and the data received from earlier screens via ${data.xxx}.

For example, in a registration Flow with three screens, Screen 1 collects data and navigates to Screen 2 passing it along:

"on-click-action": {

  "name": "navigate",

  "next": { "type": "screen", "name": "SCREEN_2" },

  "payload": {

    "name":               "${form.TextInput_name}",

    "legal_name":         "${form.TextInput_legalname}",

    "tax_id":             "${form.TextInput_taxid}",

    "state_registration": "${form.TextInput_sr}"

  }

}

Screen 2 receives those fields via ${data.xxx}, adds the new ones, and forwards them to Screen 3. The final screen uses complete (not data_exchange) and accumulates everything to send to Botmaker:

"on-click-action": {

  "name": "complete",

  "payload": {

    "name":               "${data.name}",

    "legal_name":         "${data.legal_name}",

    "tax_id":             "${data.tax_id}",

    "state_registration": "${data.state_registration}",

    "mobile_phone":       "${data.mobile_phone}",

    "landline_phone":     "${data.landline_phone}",

    "email":              "${form.TextInput_email}",

    "password":           "${form.TextInput_password}"

  }

}


Important: Without this explicit propagation, only the fields from the last screen will reach the payload; all previous data will be lost.


Step 8: Map the variables in the Botmaker platform

When the Flow completes, the payload data reaches Botmaker with automatically generated generic names (for example, screen_0_TextInput_3). To use them easily in your automations, map each field to a variable with a friendly name.

  • Open the Flow and click the Variables tab. You'll see the list of all collected fields with their generic names.
  • For each field, click the selector on the right and define the Botmaker variable where the data will be saved.


For example, the field screen_0_TextInput_0 maps to ${companyName}, screen_0_TextInput_1 to ${legalName}, and so on.

Note: If you don't configure any variable, the data will be saved with the Flow's generic names—functional, but less readable for whoever configures automations.


Checklist before publishing

Before putting your dynamic Flow into production, make sure that:

  • The routing_model covers all screens and has no missing routes.
  • The data_channel_uri points to the correct CA with the matching endpoint.
  • The ping is handled in the CA and returns { status: 'active' }.
  • Each block of the CA calls flow.send().
  • Data is propagated across screens in the on-click-action payload.
  • The final screen uses complete (not data_exchange) and doesn't call the endpoint.
  • The variables were mapped in the Flow's Variables tab.
  • The Flow was tested in the Playground with the complete JSON.

Tip: To test the Flow JSON before publishing, open Meta's Playground (developers.facebook.com/docs/whatsapp/flows/playground), replace the sample JSON with yours, click Run, and select each screen to validate the structure.


Complete annotated example

This Flow has two screens: the user types their name on the first one, the endpoint returns it in uppercase, and the second one shows the result for confirmation.

Flow JSON:

{

  "data_api_version": "3.0",

  "data_channel_uri": "https://functions.botmaker.com/whatsapp-flows/{BusinessID}/{WabaID}/dinamic",

  "routing_model": {

    "SHARE": ["RESPONSE"],

    "RESPONSE": []

  },

  "screens": [

    {

      "id": "SHARE",

      "title": "Enter your name",

      "terminal": false,

      "data": { "comment_text": { "type": "string", "__example__": "Example" } },

      "layout": {

        "type": "SingleColumnLayout",

        "children": [{

          "type": "Form", "name": "form",

          "children": [

            { "type": "TextSubheading", "text": "Type your full name:" },

            { "type": "TextArea", "name": "comment_text", "required": true,

              "helper-text": "First and last name" },

            { "type": "Footer", "label": "Continue",

              "on-click-action": {

                "name": "data_exchange",

                "payload": { "comment_text": "${form.comment_text}" }

              }}

          ]

        }]

      }

    },

    {

      "id": "RESPONSE",

      "title": "Confirmation",

      "terminal": true,

      "data": { "comment_text": { "type": "string", "__example__": "EXAMPLE" } },

      "layout": {

        "type": "SingleColumnLayout",

        "children": [{

          "type": "Form", "name": "form",

          "children": [

            { "type": "TextCaption", "text": "Confirm your name:" },

            { "type": "TextSubheading", "text": "${data.comment_text}" },

            { "type": "Footer", "label": "Finish",

              "on-click-action": {

                "name": "complete",

                "payload": { "comment_text": "${data.comment_text}" }

              }}

          ]

        }]

      }

    }

  ],

  "version": "2.1"

}

WA Flow Endpoint CA — dinamic:

// Handle ping

if (!screen || data.action === 'ping') {

  flow.data = { status: 'active' };

  flow.send();

  return;

}


// SHARE screen: return the name in uppercase

if (screen === 'SHARE') {

  flow.data = {

    comment_text: data.comment_text.toLocaleUpperCase(),

  };

  flow.nextScreen = 'RESPONSE';

  flow.send();

}


To get started with WhatsApp Flows in Botmaker, see the article "Step-by-step guide to creating WhatsApp Flows in Botmaker". To create the code action, check "Creating a code action in Botmaker", and for mapping, "Creating variables in Botmaker".


Remember to visit our Help Center for further information.