Skip to main content

How to Use whatsmeow-node with Typebot How to Use whatsmeow-node with Typebot

How to Use whatsmeow-node with Typebot

Typebot is an open-source chatbot builder with a visual flow editor. It's typically connected to WhatsApp through Evolution API (which uses Baileys). You can replace that entire layer with whatsmeow-node for a lighter, more stable connection.

How It Works

WhatsApp User

whatsmeow-node Bridge (Express)
↕ Typebot API
Typebot Flow Engine

Instead of WhatsApp → Evolution API (Baileys) → Typebot, you use WhatsApp → whatsmeow-node → Typebot directly. Fewer moving parts, less memory, more stability.

Prerequisites

  • A paired whatsmeow-node session (How to Pair)
  • A Typebot instance (self-hosted or cloud) with a published flow
  • Express: npm install express

Step 1: Get Your Typebot ID

  1. Open your Typebot flow in the editor
  2. Click Share and note the typebot ID from the URL or embed settings
  3. Your Typebot API base URL is https://typebot.io (cloud) or your self-hosted URL

Step 2: Build the Bridge

The bridge manages conversations — it starts a Typebot session per WhatsApp user and relays messages back and forth:

import { createClient } from "@whatsmeow-node/whatsmeow-node";

const client = createClient({ store: "session.db" });

const TYPEBOT_URL = process.env.TYPEBOT_URL ?? "https://typebot.io";
const TYPEBOT_ID = process.env.TYPEBOT_ID!;

// Track active Typebot sessions per WhatsApp user
const sessions = new Map<string, string>(); // JID → sessionId

// --- Start or continue a Typebot conversation ---
async function handleMessage(jid: string, text: string): Promise<string[]> {
const sessionId = sessions.get(jid);

let response;

if (!sessionId) {
// Start a new Typebot session
response = await fetch(`${TYPEBOT_URL}/api/v1/typebots/${TYPEBOT_ID}/startChat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: { type: "text", text } }),
}).then((r) => r.json());

if (response.sessionId) {
sessions.set(jid, response.sessionId);
}
} else {
// Continue existing session
response = await fetch(`${TYPEBOT_URL}/api/v1/sessions/${sessionId}/continueChat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: { type: "text", text } }),
}).then((r) => r.json());
}

// Extract text messages from Typebot response
return extractMessages(response);
}

// --- Parse Typebot response blocks into text messages ---
function extractMessages(response: Record<string, unknown>): string[] {
const messages: string[] = [];
const msgs = (response.messages as Array<{ type: string; content?: { richText?: Array<{ children: Array<{ text?: string }> }> } }>) ?? [];

for (const msg of msgs) {
if (msg.type === "text" && msg.content?.richText) {
const text = msg.content.richText
.map((block) => block.children.map((c) => c.text ?? "").join(""))
.join("\n");
if (text.trim()) messages.push(text);
}
}

return messages;
}

// --- Listen for WhatsApp messages ---
client.on("message", async ({ info, message }) => {
if (info.isFromMe || info.isGroup) return;

const text =
(message.conversation as string) ??
(message.extendedTextMessage as { text?: string } | undefined)?.text;
if (!text) return;

await client.sendChatPresence(info.chat, "composing");

try {
const replies = await handleMessage(info.sender, text);

for (const reply of replies) {
await client.sendMessage(info.chat, { conversation: reply });
// Small delay between multiple messages
if (replies.length > 1) {
await new Promise((r) => setTimeout(r, 1000));
}
}
} catch (err) {
console.error("Typebot error:", err);
await client.sendMessage(info.chat, {
conversation: "Sorry, I'm having trouble right now. Try again in a moment.",
});
}
});

async function main() {
const { jid } = await client.init();
if (!jid) {
console.error("Not paired!");
process.exit(1);
}
await client.connect();
await client.sendPresence("available");
console.log("Typebot bridge is online!");

process.on("SIGINT", async () => {
await client.sendPresence("unavailable");
await client.disconnect();
client.close();
process.exit(0);
});
}

main().catch(console.error);

Handling Input Types

Typebot flows can request different input types. Handle them by checking the input block:

function extractMessages(response: Record<string, unknown>): string[] {
const messages: string[] = [];
const msgs = (response.messages as Array<Record<string, unknown>>) ?? [];

for (const msg of msgs) {
if (msg.type === "text" && (msg.content as Record<string, unknown>)?.richText) {
const richText = (msg.content as Record<string, unknown>).richText as Array<{ children: Array<{ text?: string }> }>;
const text = richText
.map((block) => block.children.map((c) => c.text ?? "").join(""))
.join("\n");
if (text.trim()) messages.push(text);
}
}

// If the flow expects input, prompt the user
const input = response.input as Record<string, unknown> | undefined;
if (input) {
const inputType = input.type as string;
if (inputType === "choice input") {
const items = (input.items as Array<{ content: string }>) ?? [];
const options = items.map((item, i) => `${i + 1}. ${item.content}`).join("\n");
messages.push(options);
}
}

return messages;
}

Resetting Conversations

Add a !reset command to restart the Typebot flow:

client.on("message", async ({ info, message }) => {
if (info.isFromMe || info.isGroup) return;

const text = /* ... extract text ... */;
if (!text) return;

if (text.toLowerCase() === "!reset") {
sessions.delete(info.sender);
await client.sendMessage(info.chat, {
conversation: "Conversation reset! Send a message to start over.",
});
return;
}

// ... handle message as before
});

Evolution API vs whatsmeow-node

If you're currently using Evolution API with Typebot, here's what changes:

Evolution APIwhatsmeow-node bridge
WhatsApp backendBaileyswhatsmeow (Go)
Memory~50-100 MB~10-20 MB
ArchitectureSeparate serviceEmbedded in bridge
SetupDocker + confignpm install + 50 lines
StabilityBaileys fork updatesStable upstream

Common Pitfalls

Session expiration

Typebot sessions can expire. If continueChat returns an error, delete the session from the map and start a new one.

Rate limiting

Typebot flows can send multiple messages in sequence. Add a small delay (1 second) between messages to avoid WhatsApp rate limiting.

Rich content

Typebot supports images, videos, and buttons in flows. The basic bridge above only handles text. Extend extractMessages() to handle media blocks and send them via uploadMedia() + sendRawMessage().