Skip to content
Expedify
Actions — making things happen

Module · Replying and messaging

WhatsApp Question

Lesson 9 of 21 · 4 min

Every node so far finishes what it is doing and hands on. This one sends a message and then stops — the execution ends, its state is written down, and nothing is running. When the customer replies, days later if that is when they get round to it, the workflow starts again from exactly this node with their answer in hand.

That is the whole idea, and it is what separates a WhatsApp broadcast from a WhatsApp conversation. Chain a few of these and you have an intake form that lives in someone's messages: what are you interested in, when suits you, what is your budget — each answer stored under a name, each one available to everything downstream.

No workflow on this page. Like the last lesson, this node needs a connected WhatsApp provider and the tutorial organisation has none — so there is no validated workflow to show, and this lesson stays with the configuration and the branching model.

The fields

Six of eight. The other two are the integration and the list's button label.

question_text

What it holds
The question/message to send to user

buttons

What it holds
Quick reply options. Up to 3 → sent as WhatsApp reply buttons (Meta hard limit on the interactive button payload). 4 to 10 → automatically rendered as a WhatsApp list message (Meta's other primitive, capped at 10 rows total). Anything beyond 10 is silently truncated. Reply-button titles are visible up to 20 chars on Meta's side; list rows up to 24 chars.

variable_name

What it holds
Name to store the user's response (for use in subsequent nodes) Defaults to user_response.

expected_type

What it holds
Expected response type One of: any · button · text · number · date Defaults to any.

timeout_minutes

What it holds
Timeout in minutes (0 for no timeout) Defaults to 60.

timeout_message

What it holds
Message to send if user doesn't respond in time

Name the variable. variable_name defaults to user_response, which is fine for one question and useless for four — every node would be writing to the same name. Rename each one for what it holds and the rest of the workflow reads like prose.

Options are branches, not just buttons

The buttons array is the interesting field. Each entry has a label the customer sees and a payload that identifies it — and that payload becomes an output handle on the node. Tap “Machine Learning” and the workflow does not merely record the words; it leaves down the ML edge.

The switch from buttons to a list is automatic. You do not choose it.

What the customer gets
A plain question. They type whatever they like.
What the canvas gets
One output handle.

What the customer gets
Tappable reply buttons under the message.
What the canvas gets
One handle per option, plus a default for anything unmatched.

What the customer gets
A list message — a button that opens a scrollable list of choices.
What the canvas gets
Same: one handle per option, plus default.

What the customer gets
The first ten. The rest are dropped before the message is sent.
What the canvas gets
Ten handles. The eleventh option has no branch either.

The truncation is the node's, not Meta's. It is easy to assume WhatsApp is trimming your list somewhere out of sight. It is not — the node itself cuts to ten before sending, and the routing graph is cut to ten to match, so the two stay consistent. The practical rule: if you have more than ten choices, this is the wrong shape. Ask a narrowing question first.

The default path matters more than it looks. Someone who ignores your three buttons and types “can I speak to a person” goes down it. Wire it. A flow with three branches and nothing on default silently drops every customer who did not do as they were told.

What comes back

{{alias.response}}

What you get
The reply text. Always populated, whether they typed or tapped.

{{alias.response_data._matched_button}}

What you get
Which option they tapped — absent if they typed. The way to tell a chosen answer from a written one.

{{your_variable_name}}

What you get
The same reply under the name you set in variable_name.

There is one more thing that is not in any list of outputs and is worth knowing, because it looks like magic when you meet it: the node also files the reply under message, message_text, user_input and query. Those are the names a Core Agent, an LLM node or a Knowledge Base search look for by default — so putting one of those directly after this node works without wiring anything. Convenient, and worth remembering when a node downstream mysteriously already knows the answer.

The timeout

The pause is bounded. timeout_minutes defaults to 60 and caps at 1440 — a day. When it expires the node sends timeout_message and the workflow carries on down its default path, so a customer who wandered off gets a polite close rather than nothing.

Zero means wait forever, and it is a trap rather than a feature. A workflow parked with no timeout holds its state until something ends it, and nothing will. Every abandoned conversation becomes a run that never finishes, and they accumulate quietly. Pick a real number — ten minutes for a qualification question somebody is answering right now, a day for a booking they need to think about.

What breaks

The 24-hour window applies to the question too. This node sends a free-form message, so everything from the last lesson holds: if the customer has not messaged you in 24 hours, the question does not go out. A multi-step flow is therefore safe while they are actively replying and stops dead the moment they leave it overnight — which is exactly when your timeout was going to save you and cannot, because the question was never asked.

An empty payload is not the same as no payload. The handle id falls back through payload, then the label, then a generated name. Give every option an explicit short payload — ml, ai, da — and your edges stay stable when somebody rewords a label. Leave payloads blank and renaming “Machine Learning” to “ML & AI” silently moves the branch.

Expected type does not validate for free. Setting expected_type to number constrains what counts as an answer, which means a customer who types “about five thousand” has not answered. That is correct behaviour and it is also how a flow gets stuck arguing with someone. Use any and check the value yourself downstream unless you genuinely need the shape.

Try it

  1. Add one Question with three options and give each an explicit payload. Save it and look at the node on the canvas — it now has four handles where it had one.
  2. Wire a different reply to each, plus something on default. Message your business number and tap one.
  3. Now reply with free text instead of tapping. You should come out of default — and {{alias.response_data._matched_button}} should be empty. That is the check for “did they choose or did they write”.
  4. Add a fourth option and send again. The buttons become a list, with nothing in the config changed.
  5. Set timeout_minutes to 1, ask a question, and ignore it. A minute later the timeout message arrives and the workflow moves on — watch it in the execution log.

Next: Campaign Send — the other way to reach a list, where the words live in a template somebody else maintains.