Skip to main content
A template’s FLOW button is fixed: the Flow is chosen when the template is created, and changing it means editing the template and waiting for review again. A dynamic Flow picks the Flow in the send call itself. Same customer experience, no template, no approval.

Coming from a template FLOW button?

There is no button object to build. That is the one thing that trips people up on the way over: a template’s FLOW button is a component you declare and Meta approves, so the reflex is to look for the same thing here. Here the button is cta. Whatever string you put there is the label the customer taps. No components, no buttons array, no review. Two things do carry over from the template world, and both are refusals rather than surprises:
  • The Flow and the sending number must be in the same WhatsApp Business Account. If you own several numbers across several WABAs, a Flow created under one of them cannot be sent from another — you get FLOW_NOT_SENDABLE.
  • The Flow must be published (or send mode: "draft" on purpose). A deprecated Flow stops sending the moment Meta marks it so.
text is required — the Flow is a button, not the whole message. The flow block cannot be combined with templateId, list, carousel, buttons, header, or a direct media send.

The 24-hour window is not optional

This is the part you cannot see, and the reason we refuse the send instead of trying it. We measured this on 9 September 2026 against three versions of the Graph API — v22.0, v25.0 and v26.0 — with real numbers and real handsets. Outside the 24-hour window, Meta answers HTTP 200 with a wamid and silently discards the message. No error. No failure status. Nothing in any log. Eight sends, four delivered, 200 on all eight. So if we let it through, your message would vanish and neither you nor we would ever know. Instead you get:
To reopen the conversation, send a template. Templates do not depend on the window — which is exactly why a FLOW button inside a template is still the way to start a conversation.

Errors

FLOW_NOT_SENDABLE returns one message for all three causes on purpose. Telling you “it exists, but it belongs to another account” would confirm that an id you do not own is real.

data_exchange takes no screen and no data

A data_exchange Flow is answered by your endpoint, so there is nothing to seed at send time — and Meta enforces that. Send the entry screen alongside it and the whole message is rejected:
Measured in production on 10 September 2026: three sends of one account, none delivered. The initial state of a data_exchange Flow reaches it through the INIT request Meta makes to your endpoint — that is the seam, not the send. So screen and data are refused at the door instead of being silently dropped:
We refuse rather than drop, and that is deliberate. Quietly discarding data would let you believe values reached the Flow that never left our process. Remove the field, or switch to action: "navigate" — which is exactly what those two fields are for.

Getting the answers back — there are TWO destinations

Which one you get depends on the action, and mixing them up is the most common surprise here.
On a data_exchange Flow the submission itself is nearly empty, and that is not a bug. Meta hands the answers to your endpoint as they are typed, so what closes the form carries the token and little else. The answers are in submitted — see Connect your API to a Flow. If you need them in your own systems, your endpoint is where they arrive first.
When someone submits the Flow, the answers arrive on the flow.response_received webhook event and in GET /v1/flows/{id}/responses, correlated with the send that produced them. You do not have to do anything for that correlation to work. The token that links the two is generated per message and stored when the message goes out — which is also why flow_token in the request body is refused. A token chosen by the caller would let one account claim another account’s answers.
Subscribing to flow.response_received is a separate step. See Flow responses.

Two things worth knowing before you test

A Cloud API number does not receive Flows. Sending to another Meta number — even with the window open — does not deliver, and Meta still answers 200. Test with an ordinary phone. mode: "draft" works. You can try a Flow before publishing it. Remember the message reaches a real person.

Template button or dynamic Flow?