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"
.