Zernio
Zernio
PlatformsWhatsAppConnection & SetupBroadcastsTemplatesContacts & ProfilePhone NumbersCallingSandboxGroup ChatsInboxFlowsClick-to-WhatsApp AdsPricing & CostsMedia & Limits
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
WhatsApp

Flows

Build interactive WhatsApp Flows: forms, surveys, and booking experiences


Flows

WhatsApp Flows let you build native interactive forms, surveys, and booking experiences inside WhatsApp. Flows are created in DRAFT status, populated with a Flow JSON definition, then published for sending. Published flows are immutable; to update, create a new flow (optionally cloning the old one).

Create and Publish a Flow

// Step 1: Create a flow
const { data } = await zernio.whatsappflows.createWhatsAppFlow({
  body: {
    accountId: 'YOUR_WHATSAPP_ACCOUNT_ID',
    name: 'lead_capture_form',
    categories: ['LEAD_GENERATION']
  }
});
const flowId = data.flow.id;

// Step 2: Upload the Flow JSON
await zernio.whatsappflows.uploadWhatsAppFlowJson({
  path: { flowId },
  body: {
    accountId: 'YOUR_WHATSAPP_ACCOUNT_ID',
    flow_json: {
      version: '6.0',
      screens: [{
        id: 'LEAD_FORM',
        title: 'Get a Quote',
        terminal: true,
        success: true,
        layout: {
          type: 'SingleColumnLayout',
          children: [
            { type: 'TextInput', name: 'full_name', label: 'Full Name', required: true, 'input-type': 'text' },
            { type: 'TextInput', name: 'email', label: 'Email', required: true, 'input-type': 'email' },
            { type: 'Footer', label: 'Submit', 'on-click-action': { name: 'complete', payload: { full_name: '${form.full_name}', email: '${form.email}' } } }
          ]
        }
      }]
    }
  }
});

// Step 3: Publish (irreversible)
await zernio.whatsappflows.publishWhatsAppFlow({
  path: { flowId },
  body: { accountId: 'YOUR_WHATSAPP_ACCOUNT_ID' }
});

Preview a Flow

Get a public web-preview URL that renders the flow (drafts included) so you can visualize it before publishing. The link works without login and can be embedded as an iframe or shared with stakeholders. It is reused across calls (valid ~30 days); pass invalidate: true to mint a fresh one (the previous link stops working).

const { data } = await zernio.whatsappflows.getWhatsAppFlowPreview({
  path: { flowId },
  query: { accountId: 'YOUR_WHATSAPP_ACCOUNT_ID' }
});
console.log(data.preview_url); // embed as <iframe src="...&interactive=true" />

Version a Flow

A published flow is immutable, so the way to iterate is to clone it into a new draft. There are two distinct intents:

  • New version: a clone that stays in the original flow's version lineage, auto-numbered. Pass cloneFlowId plus asVersion: true. The list and create responses carry version + lineageId, and the full chain is available via the versions endpoint.
  • Clone: an independent duplicate with its own fresh lineage (version 1). Pass cloneFlowId only.
// New version of a published flow (stays in the same lineage)
const { data } = await zernio.whatsappflows.createWhatsAppFlow({
  body: {
    accountId: 'YOUR_WHATSAPP_ACCOUNT_ID',
    name: 'lead_capture_form',
    categories: ['LEAD_GENERATION'],
    cloneFlowId: 'PUBLISHED_FLOW_ID',
    asVersion: true
  }
});
console.log(`Created v${data.flow.version} (draft)`);

// Inspect the full version history (newest first)
const { data: history } = await zernio.whatsappflows.listWhatsAppFlowVersions({
  path: { flowId: data.flow.id },
  query: { accountId: 'YOUR_WHATSAPP_ACCOUNT_ID' }
});
history.versions.forEach(v => console.log(`v${v.version} - ${v.status}`));

Version history is tracked by Zernio (Meta has no native flow versioning). Omit asVersion to get an independent clone instead of a new version. A flow that was never cloned reports as version: 1 of its own lineage.

Read Flow Responses

When a customer completes a flow, their submission arrives as an nfm_reply webhook. List those submissions per flow without touching the inbox:

const { data } = await zernio.whatsappflows.listWhatsAppFlowResponses({
  query: { accountId: 'YOUR_WHATSAPP_ACCOUNT_ID', flowId: 'YOUR_FLOW_ID' }
});
data.responses.forEach(r => {
  console.log(`${r.from} @ ${r.receivedAt}:`, r.data); // r.data = submitted fields
});

Per-flow scoping works because Zernio encodes the flow id into the flow_token it auto-generates at send time (<flowId>:<uuid>), and that token round-trips through Meta inside the response. If you send a flow with your own custom flow_token, those responses won't be attributed to a flow (they still arrive via webhook). Only flows sent after this token convention shipped are attributable.

Flow Lifecycle

StatusDescription
DRAFTEditable. Upload/update JSON, change name and categories.
PUBLISHEDImmutable. Can be sent to users. To update, create a new version via cloneFlowId + asVersion.
DEPRECATEDNo longer sendable.
BLOCKEDBlocked by Meta for policy violations.
THROTTLEDTemporarily rate-limited by Meta.

Flow Categories

SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY, OTHER

Publishing a flow is irreversible. Once published, the flow and its JSON cannot be modified. To make changes, create a new version (cloneFlowId + asVersion: true) which copies the published flow into a fresh draft you can edit and publish.

See WhatsApp Flows API Reference for all endpoints.

Was this page helpful?

Inbox

Receive and reply to WhatsApp direct messages

Click-to-WhatsApp Ads

Capture CTWA clicks and forward conversion events to Meta for attribution

On this page

FlowsCreate and Publish a FlowPreview a FlowVersion a FlowRead Flow ResponsesFlow LifecycleFlow Categories