> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# WhatsApp Flows and form submissions

> The concept model behind WhatsApp Flows: a Meta-hosted interactive form, the two submission paths (in-conversation reply and Meta endpoint webhook), how a submission resolves to its tenant and a contact, where it lands, and how Flows compose with the template lifecycle — plus the troubleshooting pointers for the dispatch and resolution failure modes.

# WhatsApp Flows and form submissions

A WhatsApp Flow is a multi-screen form Meta renders natively inside WhatsApp. A completed Flow returns one structured reply instead of a free-text back-and-forth, and Orbit persists that reply as a submission you can list, join to a contact, and react to. This page explains the model; the build-and-send walkthrough is [WhatsApp Flows: interactive forms end-to-end](/guides/whatsapp/whatsapp-flows), and the field-level read surface is on [the WhatsApp channel page](/channels/whatsapp).

## What a WhatsApp Flow is

A Flow is a JSON document of screens, components (text inputs, dropdowns, radio groups, date pickers, images), and the routing between screens, hosted on Meta's infrastructure. The recipient taps a CTA button on an interactive message — "Book an appointment", "Complete this survey" — and the WhatsApp client renders the screens in-thread, with no browser hand-off. When the recipient completes the final screen, the form's answers return as one structured payload rather than a typed message.

Two properties matter for modeling:

* **The Flow lives at Meta.** Orbit authors and uploads the JSON, but Meta hosts it, validates it at upload and publish time, and renders it. Publishing is irreversible on Meta's side; lifecycle state on the Orbit side mirrors Meta's status (`DRAFT` → `PUBLISHED`, with `DEPRECATED`/`BLOCKED`/`THROTTLED` as failure states).
* **The receipt is a template-scoped object too.** Orbit stores the Flow it submitted against — the name, the uploaded JSON, and the Meta flow id — so a submission always joins back to the exact form version that produced it.

## The submissions pipeline

A Flow returns a submission over one of two paths, and Orbit handles both:

* **In-conversation reply (`nfm_reply`).** The completed form arrives as an inbound `message.received` webhook event with an `interactive` payload of type `nfm_reply`. Its `response_json` holds the answers; the `flow_token` you set on the send echoes back so you can join the submission to the session you opened.
* **Meta endpoint exchange.** For Flows bound to a Meta Flow Endpoint, a Flow's data-exchange and completion actions POST (encrypted) to Orbit's Flow endpoint route. Orbit decrypts the request, persists the submission, and replies with the encrypted response Meta's health check expects.

Both paths converge on the same storage and the same read surface. You subscribe to `message.received` for the in-conversation path; the endpoint path is server-to-server and writes the same rows.

Before either path writes anything, the inbound payload has to be resolved to its owning tenant — the same org→tenant mapping the [inbound message resolution](/concepts/inbound-message-resolution) model describes for the conversational side. After resolution the submission is recorded in that tenant's schema and fans out:

```text theme={null}
Meta webhook (Flow submission)
        │
        ▼
Resolve org → tenant schema
(uniform failure when unresolved)
        │
        ├──▶ whatsapp_flow_responses row (raw answers, verbatim)
        ├──▶ custom-field write-back (answers land on the contact)
        └──▶ whatsapp_flow_submitted CDP event (one per completion)
```

Three separate landing points, deliberately:

1. **Submission row** — every completed submission persists with its raw payload, the resolved contact (or `null` when the submitter's number matches no contact), the Meta conversation id, and the timestamps. The [`GET /api/v1/whatsapp/flows/{flowId}/submissions`](/channels/whatsapp#flow-submissions-write-to-the-contact-profile) endpoint lists them newest-first.
2. **Custom-field write-back** — answers whose field ids match a defined custom field land on the contact's profile, additively: blank answers never wipe a prior value, unmatched field ids are skipped, and one invalid answer never blocks the rest.
3. **CDP event** — each completion emits one `whatsapp_flow_submitted` event onto the contact's CDP profile. Meta retries of the same submission collapse onto one event, and the submissions list exposes the `landed_in_cdp` marker so you can confirm the event exists.

Where does this meet [inbound message routing](/concepts/inbound-message-routing)? The in-conversation `nfm_reply` reply enters the same inbound pipeline as any received message — it persists to the conversation and fires the tenant-wide received event the routing engine evaluates. A rule that matches the reply's sender or number can steer that conversation to an inbox, queue, team, or webhook; the Flow-specific persistence (submission row, write-back, CDP event) happens independently of which target the rule sends the conversation to. Endpoints of the Meta-flow-endpoint path never match routing rules — they are server-to-server writes, not inbound messages.

## Failure triage

Submissions problems cluster into three failure classes, each with a dedicated playbook:

* **Dispatch and ingestion failures** — a send that returned success but no submission rows, rows stuck with `landed_in_cdp: false`, a funnel that shows completions but a list that stays empty, a dashboard panel that disagrees with the API. These are covered end-to-end on [Troubleshoot: WhatsApp Flow submissions](/troubleshooting/whatsapp-flow-submissions), including the pagination and window defaults that cause most dashboard-versus-API disagreements.
* **Tenant-resolution and contact-resolution failures** — a submission whose submitter's phone resolves to no contact lands with `contact_id: null` and never fires the CDP event until the contact exists. The contact-resolution branch is the second section of [Troubleshoot: WhatsApp Flow submissions](/troubleshooting/whatsapp-flow-submissions); the tenant-resolution model underneath is [inbound message resolution](/concepts/inbound-message-resolution).
* **Flow-runtime failures** — when a recipient never completes the form at all (a blocked screen, an aborted encrypted exchange, a Flow stuck in `DRAFT`), the story is on [Flow execution failures](/troubleshooting/flow-executions-failed), not here: a runtime failure produces no submission.

## Composition with templates

A Flow is a template-shaped object: you author it once, Meta reviews (validates) it, and it moves through a lifecycle (`DRAFT → PUBLISHED`) before sends can reference it. The general model behind create → review → sendable → archive is [Template lifecycle and approval semantics](/concepts/template-lifecycle) — Flows apply the same pattern to structured form content instead of message bodies, with Meta's per-Flow validation errors substituting for the carrier review queue. One structural difference is worth knowing: a Flow's definition carries screens and routing in addition to copy, so its JSON is the artifact, and the [Visual Flow Builder](/channels/whatsapp#the-visual-flow-builder) plus the lifecycle endpoints are its editing surface.

The same is true on the send path: outside the 24-hour window only an approved template can open the conversation, and a Flow rides as an interactive message once the window is open — the trade-off table on the [Flows guide](/guides/whatsapp/whatsapp-flows#when-flow-vs-catalog-vs-template-content) covers when to pick a Flow versus a template versus a catalog message.

## See also

* [WhatsApp Flows: interactive forms end-to-end](/guides/whatsapp/whatsapp-flows) — the build, publish, send, and read walkthrough.
* [WhatsApp channel](/channels/whatsapp) — the Flow builder, the submissions read surface, and the custom-field write-back rules.
* [Troubleshoot: WhatsApp Flow submissions](/troubleshooting/whatsapp-flow-submissions) — the dispatch/resolution/ingestion playbook this page points at.
* [Inbound message resolution](/concepts/inbound-message-resolution) — the tenant-resolution model the pipeline depends on.
* [Inbound message routing](/concepts/inbound-message-routing) — how an inbound `nfm_reply` reply composes with routing rules.
* [Template lifecycle and approval semantics](/concepts/template-lifecycle) — the lifecycle model Flows specialize.
* [The CDP event model](/concepts/cdp-event-model) — what a `whatsapp_flow_submitted` event on a contact drives.
