Message flows run on WhatsApp in this release. Other channels are on the roadmap; a flow bound to any other channel is refused at publish.
Ask steps send a question and wait for the answer. Each one stores what came back in a variable and leaves by a branch that says what happened — the tap, the typed answer, a timeout, or an answer that did not fit.
This is the reference for the Ask group of the flow builder's palette. Every step below is listed with what it does, the settings it takes, the branches it can leave by and the one thing to keep in mind. Settings marked "templated" accept {{vars.x}}, {{contact.name}} and {{last.text}}.
Buttons
Up to three reply buttons. One out per button.
| Setting | What it does |
|---|---|
body |
The question above the buttons. |
header |
Optional header line. |
footer |
Optional footer line. |
buttons |
Up to 3 of {id, title}; each id becomes an out of this node. |
store_as |
Optional variable to store the tapped button id in. |
retries |
How many times to re-ask after an answer that does not fit, before leaving by timeout (0–5, default 1). |
timeout_minutes |
Give up waiting for a reply after this many minutes and leave by timeout. |
retry_prompt |
What to say when the answer does not fit and there is a retry left. |
Branches: timeout window_closed — One out per config.buttons[].id, plus timeout.
Keep in mind: At most three buttons of 20 characters. A tap on an older menu re-asks the current question instead of jumping.
List
Up to ten rows across sections. One out per row.
| Setting | What it does |
|---|---|
body |
The text above the list. |
header |
Optional header line. |
footer |
Optional footer line. |
button |
Label on the button that opens the list (default "Choose"). |
sections |
[{title, rows: [{id, title, description}]}], at most 10 rows in all; each row id becomes an out. |
store_as |
Optional variable to store the chosen row id in. |
retries |
How many times to re-ask after an answer that does not fit, before leaving by timeout (0–5, default 1). |
timeout_minutes |
Give up waiting for a reply after this many minutes and leave by timeout. |
retry_prompt |
What to say when the answer does not fit and there is a retry left. |
Branches: timeout window_closed — One out per row id across config.sections[].rows[], plus timeout.
Keep in mind: At most ten rows in all. The customer may also type the row number or its title.
Ask text
Wait for a typed answer and store it, with optional validation.
| Setting | What it does |
|---|---|
body |
The question to ask. |
store_as |
Variable to store the typed answer in (required). |
validate |
Optional: none, or any value kind — text, number, integer, decimal, money, percent, boolean, date, datetime, time, email, phone, url, uuid, id, regex, in, length, range, required. A miss leaves by invalid after the retries, and a hit stores the cleaned-up answer (number → a number, email → lower-cased, phone → 255712345678, date → YYYY-MM-DD). |
pattern |
The regular expression when validate is regex (or id). |
min |
With validate: the smallest number, the earliest date, or the fewest characters allowed. |
max |
With validate: the biggest number, the latest date, or the most characters allowed. |
decimals |
With validate number, decimal, money or percent: how many decimal places to keep. |
currency |
With validate money: the three-letter code, default TZS. |
country |
With validate phone: the two-letter country a leading 0 belongs to, default TZ. |
values |
With validate in: the list of answers that count, matched case-insensitively. |
accept_caption |
Optional true: the caption on a photo or document counts as the typed answer. Off, a sticker, pin, contact card, cart, form or file is re-asked, never stored ({{last.kind}} says what arrived). |
join_burst |
Default true: two or three bubbles typed within 10 seconds of each other ("John" / "Doe") are stored as one answer joined with newlines. |
retries |
How many times to re-ask after an answer that does not fit, before leaving by timeout (0–5, default 1). |
timeout_minutes |
Give up waiting for a reply after this many minutes and leave by timeout. |
retry_prompt |
What to say when the answer does not fit and there is a retry left. |
Branches: ok invalid timeout window_closed
Keep in mind: A sticker, photo, pin, contact card or cart is re-asked, never stored — accept_caption lets a photo caption count.
Confirm
A yes / no question, rendered as two buttons.
| Setting | What it does |
|---|---|
body |
The yes / no question. |
header |
Optional header line. |
footer |
Optional footer line. |
yes_label |
Label on the yes button (default "Yes"); the out stays "yes". |
no_label |
Label on the no button (default "No"); the out stays "no". |
store_as |
Optional variable to store yes or no in. |
retries |
How many times to re-ask after an answer that does not fit, before leaving by timeout (0–5, default 1). |
timeout_minutes |
Give up waiting for a reply after this many minutes and leave by timeout. |
retry_prompt |
What to say when the answer does not fit and there is a retry left. |
Branches: yes no timeout window_closed
Keep in mind: Typed ndiyo / hapana, yes / no and 1 / 2 count as well as the buttons.
WhatsApp form
A native WhatsApp Flow form. Answers land in variables.
| Setting | What it does |
|---|---|
form_id |
A Momo form (Forms page) by id or uuid. Its live version is sent, the send is logged, answers are recorded on the form and saved to its Daftari table. Takes precedence over flow_id. |
flow_id |
Only without form_id: a WhatsApp Flow id pasted from Meta's WhatsApp Manager. |
template_id |
Optional, with form_id: an approved template with an "Open form" button for the same form, used when the 24-hour window has closed. Without it a closed window leaves by window_closed. |
auto_map |
With form_id, default true: every question lands on a variable named after its saved-as key (vars.jina_kamili). map still overrides one key. |
body |
Message text sent with the form button. |
cta |
Label on the button that opens the form. |
screen |
Optional first screen id of the form. |
prefill |
Optional [{name, value}] to pre-populate form fields (templated). |
map |
Optional {form_field: variable} to copy individual answers into variables. |
store_as |
Optional variable to store all answers in as one object. |
mode |
Optional draft while the form is still a draft in Meta's Flow Builder; blank means published. |
retry_prompt |
What to say when the customer types instead of opening the form (default: "please use the Open form button above"). The original timeout stands; after the retries the node leaves by invalid, then abandoned, then timeout. |
retries |
How many times to re-ask after an answer that does not fit, before leaving by timeout (0–5, default 1). |
timeout_minutes |
Give up waiting for a reply after this many minutes and leave by timeout. |
Branches: submitted invalid abandoned timeout window_closed
Keep in mind: Pick a Momo form or build one without leaving the canvas; its answers land on variables named after the questions. A form id pasted from Meta still works.
Shop link
Send your shop page — the menu, one section, one product or a basket already filled; the order comes back to the chat.
| Setting | What it does |
|---|---|
shop_channel_id |
The shop page (a storefront channel) that opens. Blank uses the workspace's only one; with two or more, publish asks you to choose. |
target |
Where the page opens: shop (the menu, default), category (one section) or product (one product). |
category_id |
With target category: the section — a category id or slug (templated). |
product_sku |
With target product: the product's SKU (templated). |
prefill_from |
Optional variable holding lines [{sku or name, variant?, addons?, quantity?, note?}] — or a cart — to put in the basket before the customer opens it (e.g. what an AI step extracted). A product with sizes gets its first size in stock; unmatched lines are left out. |
body |
Message text above the link button, e.g. "Tap below to see today's menu." |
cta |
Label on the link button (20 characters). Default "See the menu". |
template_id |
Optional: an approved template whose URL button ends in {{1}} (…/w/{{1}}), used when the 24-hour window has closed. A utility or marketing template is also what opens the page inside WhatsApp (2K+ messaging tier). Without it a closed window leaves by window_closed. |
store_as |
Variable the basket lands on when the customer sends it, default order — the one cart shape: {ref, order_id, order_reference, reference, items[{sku, retailer_id, name, label, variant, options[{group, option, price_delta_minor}], note, quantity, unit_price_minor, line_total_minor, currency}], count, quantity, subtotal_minor, subtotal, total_minor, currency, note}. The order already exists — use order_id rather than Create order. |
confirm |
Default true: say the order summary in the chat ("Oda #1042 ✓ …") when it comes back. Off, the next step speaks. |
retries |
How many times to re-ask after an answer that does not fit, before leaving by timeout (0–5, default 1). |
timeout_minutes |
Give up waiting for a reply after this many minutes and leave by timeout. |
retry_prompt |
What to say when the answer does not fit and there is a retry left. |
Branches: submitted typed timeout window_closed
Keep in mind: A page cannot close WhatsApp's browser: after sending, the customer taps ✕ and finds the order in the chat. The order is made when they send — every price, size and add-on checked on our side, never taken from the page.
Ask for a file
Wait for a photo or document — proof of payment, an ID.
| Setting | What it does |
|---|---|
body |
What to ask for. |
accept |
Optional list of image, video, audio, document, sticker and/or mimes such as application/pdf or image/*; blank accepts anything, a mismatch leaves by wrong_type. |
store_as |
Variable to store the received file in: {url (signed link, no login, valid 30 days), path, disk, mime, type, size_bytes, filename, sha256, message_id, whatsapp_media_id, status, expires_at}. The local copy is what the flow owns — Meta's media id expires. A download the router could not complete leaves by failed with vars.__media_error. |
retries |
How many times to re-ask after an answer that does not fit, before leaving by timeout (0–5, default 1). |
timeout_minutes |
Give up waiting for a reply after this many minutes and leave by timeout. |
retry_prompt |
What to say when the answer does not fit and there is a retry left. |
Branches: ok wrong_type failed timeout window_closed
Keep in mind: Meta's media link expires, so the flow keeps its own copy; the signed link in the variable lasts 30 days.
Ask for the cart
Wait for the customer to send their WhatsApp cart, and read what is in it.
| Setting | What it does |
|---|---|
body |
What to say while waiting — usually "tap Send cart when you are done". |
store_as |
Variable to store the cart in: {catalogue_id, items[{retailer_id, quantity, unit_price_minor, line_total_minor, currency}], count, quantity, subtotal_minor, subtotal, currency, note}. Defaults to cart. A basket from the shop page (sent by its code, or through offer_link) is stored in the same shape with more on each line (variant, options, note) and order_id — that order already exists. |
offer_link |
Optional true: under the question, send a link to your shop page; the basket the customer sends from it answers this step too (the workspace's only storefront). |
retries |
How many times to re-ask after an answer that does not fit, before leaving by timeout (0–5, default 1). |
timeout_minutes |
Give up waiting for a reply after this many minutes and leave by timeout. |
retry_prompt |
What to say when the answer does not fit and there is a retry left. |
Branches: received no_items timeout window_closed
Keep in mind: Needs a connected catalogue; the cart arrives when the customer taps Send cart.
Ask for location
Request a pin with WhatsApp's own location prompt.
| Setting | What it does |
|---|---|
body |
Text sent with WhatsApp's location request. |
store_as |
Variable to store the pin ({latitude, longitude, name, address}) in. |
accept_text |
Optional true: a typed address counts and is stored as {address}. Off, typing re-asks with the location request, then leaves by invalid. |
retries |
How many times to re-ask after an answer that does not fit, before leaving by timeout (0–5, default 1). |
timeout_minutes |
Give up waiting for a reply after this many minutes and leave by timeout. |
retry_prompt |
What to say when the answer does not fit and there is a retry left. |
Branches: ok invalid timeout window_closed
Keep in mind: Uses WhatsApp's own location request; a typed address only counts with accept_text.
Link button
A button that opens a URL. It does not wait for a reply.
| Setting | What it does |
|---|---|
body |
The message text above the button. |
display_text |
Label on the button. |
url |
The URL the button opens (templated). |
Branches: next window_closed
Keep in mind: It does not wait — the next step runs at once.
Numbered menu
A plain-text "1. … 2. …" menu — how a list reads on SMS.
| Setting | What it does |
|---|---|
body |
The text above the numbered items. |
items |
[{id, label}] rendered as "1. label"; each id becomes an out, and the customer replies with the number. |
store_as |
Optional variable to store the chosen item id in. |
retries |
How many times to re-ask after an answer that does not fit, before leaving by timeout (0–5, default 1). |
timeout_minutes |
Give up waiting for a reply after this many minutes and leave by timeout. |
retry_prompt |
What to say when the answer does not fit and there is a retry left. |
Branches: timeout window_closed — One out per config.items[].id, plus timeout.
Keep in mind: A plain-text menu; the customer answers with the number (or the title).
Where next
- Open the builder at Message flows and drag any of these onto the canvas — the panel on the right shows the same settings.
- New to flows? Start with Build your first WhatsApp flow.
- The other groups: Say · Think · Decide · Wait · Do · Route.
- The rules every flow lives under: the 24-hour window, payments, what we keep and why a flow stops.