Flowise on WhatsApp through the official API, with ZapClaw

In short: ZapClaw delivers each WhatsApp message to a small relay, the relay asks your Flowise flow through its Prediction API, and the answer goes back through ZapClaw. Messages travel on Meta's official Cloud API, and the number keeps working in the WhatsApp Business app on your phone. Chatflows and agentflows both work, and you need no Meta app of your own.

Connect your number in minutes and give your Flowise agent the WhatsApp number your customers already use, in the cloud or self-hosted.

Business plan: US$ 29 per month, with unlimited numbers

Start for free See the relay

What you need to connect

You do not need a company tax ID or a verified business to connect. The full checklist.

Why ZapClaw for a Flowise agent on WhatsApp

Why a relay sits in between

Flowise runs a flow from its Prediction API, with the message in a question field, and returns the answer in the same HTTP response. ZapClaw's webhook carries its own JSON and does not read an answer from the response, so a small relay sits in between: it turns each message into a question, and the answer into a send through ZapClaw.

The relay below is under 70 lines of Node.js, with nothing to install. If you already run n8n, it can be the relay instead, as shown further down.

Set it up in four steps

  1. In Flowise, save your chatflow or agentflow and note its flow ID. If you assigned an API key to the flow, note that key too.
  2. In ZapClaw, connect your number, create an API key and copy the Signing secret from the Webhook page.
  3. Run the relay below with those values on a server with a public HTTPS address, and paste that address under Webhook in ZapClaw. ZapClaw delivers only to a public HTTPS address.
  4. Press Send test event in ZapClaw: the relay answers 200 and asks Flowise nothing. Then send a WhatsApp message to your number from another phone: the flow answers.

The relay, ready to run

Save it as relay.mjs and start it with node relay.mjs on Node.js 18 or newer. It reads its settings from the environment: ZAPCLAW_API_KEY, ZAPCLAW_SECRET (the signing secret), FLOWISE_URL (your Flowise address), FLOW_ID, FLOWISE_API_KEY when the flow has one, and PORT, the port the relay listens on (8787 by default), which your HTTPS proxy forwards to.

// relay.mjs: ZapClaw webhook in, Flowise Prediction API, the answer out through ZapClaw.
// Node.js 18 or newer, nothing to install. Start it with: node relay.mjs
import { createServer } from 'node:http';
import { createHmac, timingSafeEqual } from 'node:crypto';

const ZAPCLAW_API_KEY = process.env.ZAPCLAW_API_KEY; // zc_live_...
const ZAPCLAW_SECRET = process.env.ZAPCLAW_SECRET; // the webhook signing secret
const FLOWISE_URL = process.env.FLOWISE_URL || 'http://localhost:3000'; // your Flowise address
const FLOW_ID = process.env.FLOW_ID; // the ID of your chatflow or agentflow
const FLOWISE_API_KEY = process.env.FLOWISE_API_KEY; // only when the flow has an API key

const queues = new Map(); // one message at a time per customer, in order

const isSigned = (raw, header = '') => {
  const hmac = createHmac('sha256', ZAPCLAW_SECRET).update(raw).digest('hex');
  const expected = Buffer.from(`sha256=${hmac}`);
  const given = Buffer.from(String(header));
  return given.length === expected.length && timingSafeEqual(given, expected);
};

const answer = async ({ data, account }) => {
  // The customer is the session: Flowise keeps one conversation memory per sessionId.
  const res = await fetch(new URL(`api/v1/prediction/${FLOW_ID}`, FLOWISE_URL), {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      ...(FLOWISE_API_KEY && { Authorization: `Bearer ${FLOWISE_API_KEY}` }),
    },
    body: JSON.stringify({
      question: data.text.body,
      overrideConfig: { sessionId: data.user_id || data.from },
    }),
  });
  if (!res.ok) throw new Error(`Flowise answered ${res.status}`);
  const { text } = await res.json();
  if (!text) return;
  await fetch('https://api.zapclaw.app/api/v1/send/text', {
    method: 'POST',
    headers: { 'X-API-Key': ZAPCLAW_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      from: account.display_phone_number || account.phone_number_id,
      to: data.from || data.user_id,
      text: text.slice(0, 4096),
    }),
  });
};

createServer((req, res) => {
  const chunks = [];
  req.on('data', (chunk) => chunks.push(chunk));
  req.on('end', () => {
    const raw = Buffer.concat(chunks);
    if (!isSigned(raw, req.headers['x-zapclaw-signature'])) return res.writeHead(401).end();
    res.writeHead(200).end(); // answer ZapClaw now: it waits 10 seconds, a flow can take longer
    let event;
    try {
      event = JSON.parse(raw.toString('utf8'));
    } catch {
      return;
    }
    if (event.event !== 'message.received' || event.data?.type !== 'text') return;
    const key = event.data.user_id || event.data.from;
    const next = (queues.get(key) || Promise.resolve())
      .then(() => answer(event))
      .catch((err) => console.error(err));
    queues.set(key, next);
  });
}).listen(process.env.PORT || 8787);

What it does: it checks the X-ZapClaw-Signature header, answers ZapClaw at once (ZapClaw waits 10 seconds, and a model can take longer) and asks Flowise one message at a time per customer, so the answers keep their order. The flow's answer comes back in the text field and goes out as a WhatsApp message.

In an agentflow, keep the Start node on Chat Input: the relay sends the message as question, and a Form Input start expects a form object instead.

Or use n8n as the relay

Already run n8n? Import the flow from our n8n page and add an HTTP Request node between the filter and the reply that posts to your flow's Prediction API, with question set to {{ $json.body.data.text.body }} and overrideConfig.sessionId to {{ $json.body.data.user_id || $json.body.data.from }}. After that node, $json holds Flowise's answer, so in the reply set text to {{ $json.text }} and make from and to read the webhook node: {{ $('ZapClaw webhook').item.json.body.account.display_phone_number || $('ZapClaw webhook').item.json.body.account.phone_number_id }} and {{ $('ZapClaw webhook').item.json.body.data.from || $('ZapClaw webhook').item.json.body.data.user_id }}.

Calling ZapClaw from inside Flowise

Flowise can call ZapClaw's API on its own too: the HTTP node of an agentflow, or a Custom Tool the agent chooses to use, with an X-API-Key header. For example, a tool that alerts a salesperson on WhatsApp when a customer asks for a quote. Inside a Custom Tool, $flow.sessionId holds the customer, because the relay passes it as the session, and a Flowise variable read through $vars can hold your ZapClaw key. The call itself, as a curl command:

curl -X POST https://api.zapclaw.app/api/v1/send/text \
  -H "X-API-Key: zc_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "from": "15551617409", "to": "15555550100", "text": "A customer asked for a quote" }'

from is your connected number, which the webhook brings in account.display_phone_number, and to is the person you write to. A sent message answers 201. Free text works within 24 hours of that person's last message to your number; outside that window Meta accepts only an approved template, through POST /api/v1/send/template.

What reaches the relay

Each message comes as a message.received event: the text in data.text.body, the sender in data.from (or data.user_id for a contact who shows only a username), the contact's name in data.contact_name and your number in account.display_phone_number.

{
  "event": "message.received",
  "sequence": 1042,
  "timestamp": "2026-10-15T14:00:00.000Z",
  "data": {
    "from": "15555550100",
    "user_id": "US.1A2B3C4D5E6F7G8H9I0J",
    "contact_name": "Maria Silva",
    "message_id": "wamid.HBg...",
    "type": "text",
    "timestamp": "1792072800",
    "text": { "body": "Do you deliver on Saturdays?" }
  },
  "account": {
    "phone_number_id": "964283400112599",
    "display_name": "My Business",
    "display_phone_number": "+1 555 161-7409"
  }
}

The relay passes on only incoming text messages: the dashboard's test event and every other event get a 200 and nothing more. A voice note or an image carries a media_url you download with your API key, if you want to hand it to Flowise later. Every event and field is in the docs.

What it costs

Meta bills its WhatsApp message fees to you directly, with no ZapClaw markup. Every plan and price.

Who it is for

Teams that built an AI agent, an assistant over their own documents or a support triage flow in Flowise and want it on the WhatsApp number their customers already use. Agencies connect their clients' numbers on the Agency plan, each client isolated from the others. It does not fit bulk broadcasting.

Go further

Sources

Flowise's documentation checked on 15 October 2026. Features change, so check each source before you build.

Put your Flowise agent on WhatsApp today

Start on the free plan: one number, no card. Move to the Business plan when you need more numbers or more messages.

Start for free Read the webhook docs