
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
- Open your Typebot flow in the editor
- Click Share and note the typebot ID from the URL or embed settings
- 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 API | whatsmeow-node bridge | |
|---|---|---|
| WhatsApp backend | Baileys | whatsmeow (Go) |
| Memory | ~50-100 MB | ~10-20 MB |
| Architecture | Separate service | Embedded in bridge |
| Setup | Docker + config | npm install + 50 lines |
| Stability | Baileys fork updates | Stable upstream |
Common Pitfalls
Typebot sessions can expire. If continueChat returns an error, delete the session from the map and start a new one.
Typebot flows can send multiple messages in sequence. Add a small delay (1 second) between messages to avoid WhatsApp rate limiting.
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().







