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:
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' }.
Step 1: Create the Client Action (CA)
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
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.
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.
Before putting your dynamic Flow into production, make sure that:
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.
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.