Pular para o conteúdo principal

Como Usar o whatsmeow-node com Typebot Como Usar o whatsmeow-node com Typebot

Como Usar o whatsmeow-node com Typebot

O Typebot é um construtor de chatbots open-source com editor visual de fluxos. Normalmente é conectado ao WhatsApp através da Evolution API (que usa Baileys). Você pode substituir toda essa camada pelo whatsmeow-node para uma conexão mais leve e estável.

Como Funciona

WhatsApp User

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

Em vez de WhatsApp, Evolution API (Baileys), Typebot, você usa WhatsApp, whatsmeow-node, Typebot diretamente. Menos peças móveis, menos memória, mais estabilidade.

Pré-requisitos

  • Uma sessão pareada do whatsmeow-node (Como Parear)
  • Uma instância do Typebot (self-hosted ou cloud) com um fluxo publicado
  • Express: npm install express

Passo 1: Obter o ID do Typebot

  1. Abra seu fluxo do Typebot no editor
  2. Clique em Share e anote o ID do typebot na URL ou nas configurações de embed
  3. A URL base da API do Typebot é https://typebot.io (cloud) ou a URL do seu self-hosted

Passo 2: Construir a Ponte

A ponte gerencia as conversas — ela inicia uma sessão do Typebot por usuário do WhatsApp e repassa as mensagens nos dois sentidos:

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);

Tratando Tipos de Input

Fluxos do Typebot podem solicitar diferentes tipos de input. Trate-os verificando o bloco input:

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;
}

Resetando Conversas

Adicione um comando !reset para reiniciar o fluxo do Typebot:

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

Se você está usando a Evolution API com Typebot atualmente, veja o que muda:

Evolution APIponte whatsmeow-node
Backend WhatsAppBaileyswhatsmeow (Go)
Memória~50-100 MB~10-20 MB
ArquiteturaServiço separadoEmbutido na ponte
SetupDocker + confignpm install + 50 linhas
EstabilidadeAtualizações de fork do BaileysUpstream estável

Erros Comuns

Expiração de sessão

Sessões do Typebot podem expirar. Se o continueChat retornar um erro, delete a sessão do map e inicie uma nova.

Rate limiting

Fluxos do Typebot podem enviar múltiplas mensagens em sequência. Adicione um pequeno delay (1 segundo) entre as mensagens para evitar rate limiting do WhatsApp.

Conteúdo rico

O Typebot suporta imagens, vídeos e botões nos fluxos. A ponte básica acima só trata texto. Expanda extractMessages() para tratar blocos de mídia e enviá-los via uploadMedia() + sendRawMessage().