Skip to main content
Requires a WhatsApp Business Account (WABA) with Flow feature enabled. Works with Meta access tokens or Kapso API keys.

Send a flow message

await client.messages.sendInteractiveFlow({
  phoneNumberId: '123',
  to: '56961567267',
  bodyText: 'Start the onboarding',
  parameters: {
    flowId: '1197715005513101',
    flowCta: 'Open', // required, 1-20 chars
    flowMessageVersion: '3', // defaults to "3"
    flowAction: 'navigate',
    flowActionPayload: { screen: 'WELCOME' }
  }
});

Create a flow

const { id } = await client.flows.create({
  wabaId: 'WABA_ID',
  name: 'Onboarding',
  categories: ['OTHER'],
  endpointUri: 'https://example.com/flows/endpoint',
  flowJson: {
    version: '7.2',
    dataApiVersion: '3.0',
    routingModel: { WELCOME: [] },
    screens: [
      {
        id: 'WELCOME',
        terminal: true,
        layout: {
          type: 'SingleColumnLayout',
          children: [
            { type: 'TextHeading', text: 'Welcome!' },
            { type: 'Footer', label: 'Finish', onClickAction: { name: 'complete', payload: {} } }
          ]
        }
      }
    ]
  }
});
Write your Flow JSON in camelCase—the SDK converts it for you:
const myFlowJson = {
  version: '7.2',
  dataApiVersion: '3.0',
  routingModel: { BOOKING: [] },
  screens: [
    {
      id: 'BOOKING',
      title: 'Booking',
      terminal: true,
      data: {
        availableSlots: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, title: { type: 'string' } } }, __example__: [{ id: '1', title: '09:00' }] },
        isDropdownVisible: { type: 'boolean', __example__: false }
      },
      layout: { type: 'SingleColumnLayout', children: [{ type: 'TextHeading', text: 'Pick a time' }] }
    }
  ]
};
Meta’s fixed keys (like component type) stay as-is. Your custom keys can be camelCase.

Update flow JSON

await client.flows.updateAsset({
  flowId: id,
  json: myFlowJson,            // or file: Blob/ArrayBuffer
  phoneNumberId: '1234567890'  // when using Kapso proxy
});
Safe to call repeatedly during development.

Publish & deprecate

await client.flows.publish({ flowId: id, phoneNumberId: '1234567890' });
await client.flows.deprecate({ flowId: id, phoneNumberId: '1234567890' });

Preview flows

const { preview } = await client.flows.preview({
  flowId: id,
  phoneNumberId: '1234567890',
  interactive: true
});
console.log(preview.previewUrl);

Get & list flows

const metadata = await client.flows.get<{ id: string; name: string }>({ flowId: id, fields: 'id,name' });
const list = await client.flows.list<{ data: Array<{ id: string }> }>({ wabaId: 'WABA_ID', limit: 10 });

Handle data endpoint callbacks

import express from 'express';
import { receiveFlowEvent, respondToFlow } from '@kapso/whatsapp-cloud-api/server';

const app = express();
app.post('/flows/onboarding', express.raw({ type: '*/*' }), async (req, res) => {
  try {
    const ctx = await receiveFlowEvent({
      rawBody: req.body as Buffer,
      phoneNumberId: process.env.PHONE_ID!,
      getPrivateKey: async () => process.env.FLOW_PRIVATE_KEY_PEM!
    });

    const reply = respondToFlow({ screen: ctx.screen, data: {} });
    res.status(reply.status).set(reply.headers).send(reply.body);
  } catch (e: any) {
    const status = e?.status ?? 500;
    res.status(status).set(e?.headers ?? {}).send(e?.body ?? { error: 'unexpected' });
  }
});

Using Kapso proxy

const client = new WhatsAppClient({
  baseUrl: 'https://app.kapso.ai/api/meta/',
  kapsoApiKey: process.env.KAPSO_API_KEY!
});

await client.flows.publish({ flowId: 'FLOW', phoneNumberId: '1234567890' });
You can use either phoneNumberId or businessAccountId when publishing.

Errors & types

Meta returns validation errors with snake_case field names. The SDK converts them to camelCase and adds helpful hints. Export FlowInteractiveInput for typing. The flowMessageVersion defaults to "3".
I