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

# Flows

> Create, publish, and send WhatsApp Flows — and collect submissions.

WhatsApp **Flows** are Meta-hosted interactive forms (screens of inputs and navigation) opened from a chat. LazyApp stores a workspace mirror of each flow, publishes to Meta, and records completions on webhooks and [`GET /v1/flows/{flow}/responses`](https://docs.lazyapp.in/api-reference/flows/list-flow-responses).

Do not confuse Flows with [workflows](https://docs.lazyapp.in/api-reference/workflows/list-workflows.md) (LazyApp automation graphs) or [templates](https://docs.lazyapp.in/sending/templates.md) (approved outbound message shells). A Flow can be attached to a template button or sent as an interactive message inside the [24-hour window](https://docs.lazyapp.in/sending/messages.md#the-24-hour-window).

> [!NOTE]
>
> WhatsApp Flows are on the Growth and Pro plans. On a smaller plan you can still open and delete existing Flows, but not create, change or publish them, or send them from the inbox. Test mode is never limited. See [Plans and limits](https://docs.lazyapp.in/billing/pricing-and-tiers.md#feature-gating).

> [!WARNING]
>
> **Coexistence numbers** report `flows: false` and `flows.data_endpoint: false` on [`GET /v1/capabilities`](https://docs.lazyapp.in/concepts/capabilities). Build Flow onboarding for Cloud-API-only numbers. See [WhatsApp Business app coexistence](https://docs.lazyapp.in/guides/connecting-whatsapp.md#whatsapp-business-app-coexistence).

## Check before you build

```bash theme={null}
curl https://lazyapp.in/v1/capabilities \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

| Capability | Meaning |
| - | - |
| `flows` | Create / publish / send Flows on this connection |
| `flows.data_endpoint` | Dynamic Flows with your HTTPS data endpoint |
| `messages.flow` | Interactive `type: flow` sends |

**Required scope for management APIs:** `flows:write` (list, create, publish, deprecate, responses, endpoint health). Sending a Flow message also needs `messages:send`.

## What LazyApp supports

| Surface | Status |
| - | - |
| [`GET /v1/flows`](https://docs.lazyapp.in/api-reference/flows/list-flows) | List (cursor-paginated; filter `status`) |
| [`POST /v1/flows`](https://docs.lazyapp.in/api-reference/flows/create-a-flow) | Create draft + upload Flow JSON to Meta |
| [`GET /v1/flows/{flow}`](https://docs.lazyapp.in/api-reference/flows/retrieve-a-flow) | One flow by LazyApp `uuid` or Meta `provider_flow_id` |
| [`PUT /v1/flows/{flow}`](#update-or-delete-a-draft) | Replace a draft's name, categories, Flow JSON or endpoint |
| [`DELETE /v1/flows/{flow}`](#update-or-delete-a-draft) | Delete a draft at Meta and in LazyApp |
| [`POST /v1/flows/{flow}/publish`](https://docs.lazyapp.in/api-reference/flows/publish-a-flow) | Make sendable |
| [`POST /v1/flows/{flow}/deprecate`](https://docs.lazyapp.in/api-reference/flows/deprecate-a-flow) | Stop new sends; open sessions continue |
| [`GET /v1/flows/{flow}/health`](#can-it-be-sent) | Ask Meta whether the flow can be sent, and what blocks it |
| [`GET /v1/flows/{flow}/endpoint-health`](https://docs.lazyapp.in/api-reference/flows/check-data-endpoint-health) | Probe the data endpoint |
| [`GET /v1/flows/{flow}/responses`](https://docs.lazyapp.in/api-reference/flows/list-flow-responses) | Completion payloads |
| [`POST /v1/flows/copy`](#copy-flows-from-another-number) | Copy flows from another number's WhatsApp Business account |
| Send Flow | [`POST /v1/messages`](https://docs.lazyapp.in/sending/messages) with `interactive.type: flow`, or a template with a flow button |
| [`GET /v1/flows/starters`](https://docs.lazyapp.in/guides/flows#starters) / `POST` | List and materialise starter drafts |
| [`POST /v1/flows/{flow}/duplicate`](https://docs.lazyapp.in/guides/flows#duplicate) | Clone into a new draft |
| Update published Flow JSON in place | **Not allowed** by Meta. Duplicate, then publish |
| Data endpoint with encryption | LazyApp hosts the endpoint and handles the encryption. See [data endpoint](#data-endpoint) |

## Lifecycle

```
draft → publish → published (or throttled) → deprecate → deprecated
```

Meta may also mark a flow `blocked` or `throttled`. Only **`published`** and **`throttled`** are sendable. Published flows cannot be edited in place — [duplicate](#duplicate) into a new draft, then publish.

## Starters

```bash theme={null}
curl https://lazyapp.in/v1/flows/starters \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"

curl -X POST https://lazyapp.in/v1/flows/starters \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "starter": "lead_capture", "name": "Lead capture" }'
```

## Duplicate

```bash theme={null}
curl -X POST https://lazyapp.in/v1/flows/{uuid}/duplicate \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Lead capture (copy)" }'
```

## Create a flow

```bash theme={null}
curl -X POST https://lazyapp.in/v1/flows \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: flow-lead-1" \
  -d '{
    "name": "Lead capture",
    "categories": ["LEAD_GENERATION"],
    "flow_json": {
      "version": "7.0",
      "screens": [
        {
          "id": "LEAD",
          "terminal": true,
          "layout": {
            "type": "SingleColumnLayout",
            "children": []
          }
        }
      ]
    }
  }'
```

| Field | Required | Notes |
| - | - | - |
| `name` | Yes | Max 200 characters |
| `categories` | Yes | At least one Meta Flow category string (for example `LEAD_GENERATION`, `APPOINTMENT_BOOKING`) |
| `flow_json` | Yes | Object or JSON string — must include `screens`, unique screen `id`s, and at least one `terminal: true` screen |
| `data_endpoint_uri` | No | **HTTPS** URL of your server for dynamic Flows. LazyApp forwards decrypted requests to it |
| `public_key` | No | Accepted for compatibility and ignored: LazyApp manages the number's key |

Invalid Flow JSON returns `422` with field errors before Meta is called. On success the flow is created at Meta and stored as `draft` with a `provider_flow_id`.

```json theme={null}
{
  "uuid": "9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
  "name": "Lead capture",
  "status": "draft",
  "categories": ["LEAD_GENERATION"],
  "provider_flow_id": "flow-777",
  "version": 1,
  "data_endpoint_uri": null,
  "endpoint_health": null,
  "published_at": null,
  "created_at": "2026-09-02T08:00:00+00:00"
}
```

Console starters (`lead_capture`, `appointment_booking`, and others) produce the same draft shape without writing Flow JSON by hand.

## Publish and deprecate

```bash theme={null}
curl -X POST https://lazyapp.in/v1/flows/9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d/publish \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Idempotency-Key: flow-lead-1-publish"
```

Publishing emits `flow.published` (or `flow.failed` on Meta error). Deprecate with `POST …/deprecate` when you want to stop attaching the flow to new messages. If Meta later throttles or blocks the flow because its endpoint is failing, you get `flow.status_changed`.

## Update or delete a draft

`PUT /v1/flows/{flow}` takes the same body as create and replaces the draft. A published flow answers `422` with `code: flow_not_editable`.

`DELETE /v1/flows/{flow}` deletes a draft at Meta and in LazyApp, and answers `204`. A published flow answers `422` with `code: flow_not_deletable`: deprecate it instead.

## Can it be sent?

Sending a flow depends on the flow, your WhatsApp Business account, your Meta business and LazyApp's app. Meta rates each one and LazyApp asks for your number specifically:

```bash theme={null}
curl https://lazyapp.in/v1/flows/9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d/health \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

```json theme={null}
{
  "can_send_message": "BLOCKED",
  "problems": ["endpoint_uri: You need to set the endpoint URI before you can send or publish a flow."],
  "notes": [],
  "entities": [ { "entity_type": "FLOW", "can_send_message": "BLOCKED", "errors": [ … ] } ]
}
```

`can_send_message` is `AVAILABLE`, `LIMITED` (sendable, see `notes`) or `BLOCKED` (see `problems`). The result is kept on the flow as `can_send_message`. In the console, use **Check it can be sent** on a flow.

## Copy flows from another number

If your workspace has numbers on two WhatsApp Business accounts owned by the same Meta business, copy the flows from one into the workspace's main account:

```bash theme={null}
curl -X POST https://lazyapp.in/v1/flows/copy \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" -H "Content-Type: application/json" \
  -d '{ "source_connection_uuid": "…", "flow_names": ["appointment-booking"] }'
```

Leave out `flow_names` to copy every flow (Meta copies up to 100 per request). Copies keep their names and status; a name that already exists is reported under `failed` with Meta's reason. In the console: **Flows → ⋯ → Copy flows from another number**.

## Data endpoint

Meta calls a data endpoint for flows that load data while the customer fills them in. That traffic is encrypted with a key pair registered on your number. LazyApp is always the endpoint Meta calls: it keeps the key, decrypts each request and either answers from the Flow JSON (flows without a `data_endpoint_uri`) or forwards plain JSON to your `data_endpoint_uri`, signed with `X-LazyApp-Signature: sha256=…` using the flow's signing secret. Your reply is encrypted back for Meta. Set up the number's key once under **Flows → Secure flow connection**.

### Verify the signature

`POST /v1/flows` returns the flow's `endpoint_secret` (`whfs_…`) when you set a `data_endpoint_uri`, and so does the `PUT` that first adds one. It is shown only then, like a webhook secret, so store it with your endpoint's config. Lost it, or need to rotate? `POST /v1/flows/{flow}/rotate-endpoint-secret` returns a new one; the old one stops verifying at once.

```bash theme={null}
curl -X POST https://lazyapp.in/v1/flows/{flow}/rotate-endpoint-secret \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

Check every forwarded request before trusting it: compute HMAC-SHA256 of the raw body with the secret and compare it, in constant time, with the hex after `sha256=` in `X-LazyApp-Signature`.

### Data endpoint health

Probe it the way Meta does:

```bash theme={null}
curl https://lazyapp.in/v1/flows/9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d/endpoint-health \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

```json theme={null}
{
  "reachable": true,
  "status": 200,
  "latency_ms": 84,
  "error": null,
  "checked_at": "2026-09-02T08:05:00+00:00"
}
```

LazyApp sends an encrypted, signed `ping` to its own hosted endpoint, exactly as Meta does, and records the result on the flow.

## Send a Flow message

Inside the customer-service window (or via a template that opens the Flow):

```bash theme={null}
curl -X POST https://lazyapp.in/v1/messages \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: flow-send-1" \
  -d '{
    "type": "interactive",
    "to": "+919812345670",
    "interactive": {
      "type": "flow",
      "body": { "text": "Complete the form" },
      "action": {
        "name": "flow",
        "parameters": {
          "flow_message_version": "3",
          "flow_id": "flow-777",
          "flow_token": "session-token-1",
          "flow_cta": "Open",
          "flow_action": "navigate",
          "flow_action_payload": {
            "screen": "LEAD",
            "data": {}
          }
        }
      }
    }
  }'
```

Use Meta's `provider_flow_id` as `flow_id`. Workflows can launch published flows with a generated `flow_token` via the `launch_flow` node.

## Collect responses

When the contact finishes, Meta delivers an interactive `nfm_reply`. LazyApp:

1. Stores a `WhatsAppFlowResponse` linked to the flow
2. Emits `flow.response_received`
3. Exposes the payload on the list API

```bash theme={null}
curl "https://lazyapp.in/v1/flows/9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d/responses?limit=25" \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

```json theme={null}
{
  "data": [
    {
      "uuid": "…",
      "flow_token": "tok-123",
      "payload": {
        "flow_token": "tok-123",
        "full_name": "Ada Lovelace"
      },
      "received_at": "2026-09-02T08:10:00+00:00"
    }
  ],
  "next_cursor": null
}
```

Prefer webhooks for live capture; list + console CSV export are for backfill and ops.

## Provisioning with automation packs

[`PUT /v1/automation-packs/provision`](https://docs.lazyapp.in/sending/automation-packs) can seed starter Flows for a vertical (`include_flows: true`) alongside events and workflows — useful for platform customers.

## What we skip (Zernio-style)

| Zernio surface | LazyApp |
| - | - |
| `/v1/whatsapp/flows/*` | Flat `/v1/flows/*` |
| `POST …/flows/send` | [`POST /v1/messages`](https://docs.lazyapp.in/sending/messages) interactive |
| Flow JSON / versions / preview as separate public routes | Create + show (`flow_json` on retrieve); versioning via console clone |
| Managed data-endpoint hosting | LazyApp hosts and encrypts; your `data_endpoint_uri` gets plain signed JSON |
| `accountId` on every call | Workspace from API key |

## Related

* [Capabilities](https://docs.lazyapp.in/concepts/capabilities.md) — `flows` flags
* [Messages](https://docs.lazyapp.in/sending/messages.md) — interactive types and the 24-hour window
* [Webhooks — Flows](https://docs.lazyapp.in/webhooks/events.md#flows) — `flow.published`, `flow.failed`, `flow.response_received`, `flow.status_changed`
* [Automation packs](https://docs.lazyapp.in/sending/automation-packs.md) — seed Flows for a vertical
* [Pending gaps vs Zernio](https://docs.lazyapp.in/pending-gaps-zernio.md) — hosted endpoint backlog


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.