This page is also available in English.

Publicado em 11 min de leitura

Foto de Ice Family no Unsplash
A maioria dos tutoriais de “bot no WhatsApp” começa com um QR Code e um pong. Essa demo funciona por cinco minutos. Automação de verdade quebra por outro motivo: mensagens são eventos, e o seu código ou é desenhado em cima disso ou luta com árvores de if, recibos perdidos e tempestades de reconnect.
Este guia é uma referência para construir automações event-driven em Node.js com dois clientes não oficiais populares — Baileys e whatsapp-web.js — e para saber quando migrar para os webhooks oficiais da WhatsApp Cloud API. Você sai com cinco padrões reutilizáveis, uma comparação clara e limites duros de compliance.
Compliance em primeiro lugar. Os Termos de Serviço do WhatsApp proíbem bulk messaging, auto-messaging e automação não autorizada semelhante em contas pessoais. A Central de Ajuda afirma que mensagens em massa e automatizadas sempre violaram os Termos, com enforcement por banimentos e, em casos graves, ação legal. Baileys e whatsapp-web.js não são a WhatsApp Business Platform. Use clientes não oficiais só onde você aceita risco de ban (experimentos pessoais, números descartáveis). Para automação comercial com clientes em escala, use o app WhatsApp Business ou a WhatsApp Business Platform.
Arquitetura orientada a eventos (EDA) significa que o sistema reage a coisas que aconteceram — mensagem chegou, recibo virou read, o socket fechou — em vez de ficar em polling ou encadear scripts síncronos “faz A depois B”.
No Node.js essa ideia é nativa. O runtime gira em torno do event loop, e o EventEmitter do módulo events é o pub/sub in-process que quase toda biblioteca de rede estende. Os clientes de WhatsApp usam o mesmo modelo:
| Camada | O que emite | O que você escuta |
|---|---|---|
| Baileys | sock.ev (BaileysEventMap tipado) | messages.upsert, connection.update, creds.update, … |
| whatsapp-web.js | Client (EventEmitter) | message, qr, ready, disconnected, … |
| Cloud API | Webhooks HTTPS da Meta | campo messages (entrada + status), updates de conta, … |
A automação que você quer raramente é “enviar uma string”. É um pipeline: ingerir evento → normalizar → decidir → efeitos colaterais (resposta, CRM, ticket) → observar resultados (ack, falha, retry).
Baileys é uma biblioteca TypeScript que fala o protocolo multi-device do WhatsApp Web via WebSockets. Ela não controla Chromium. A documentação oficial enfatiza que conecta uma conta do app pessoal ou Business via Dispositivos vinculados, não é WABA e não é afiliada ao WhatsApp. Os mantenedores desencorajam spam, stalkerware e mensagens em massa/automatizadas.
Na linha v7, o projeto documenta breaking changes (veja as notas de migração). O ecossistema npm historicamente usou @whiskeysockets/baileys; as instruções em baileys.wiki hoje mostram npm install baileys. Fixe a versão e leia o changelog antes de atualizar.
Modelo mental:
creds.update precisa ser salvo — a docs avisa que pular isso quebra auth e descriptografia).makeWASocket.sock.ev.loggedOut.whatsapp-web.js (Apache-2.0) sobe ou anexa um Chromium gerenciado via Puppeteer, carrega o WhatsApp Web e chama o store interno. A docs exige Node.js 18+. A API é acessível (client.on('message', …)), com cobertura ampla de features (mídia, grupos, enquetes, canais e mais — veja a tabela do projeto).
Trade-off: você paga em RAM, CPU e fragilidade. Toda mudança de UI ou API interna do WhatsApp Web pode quebrar selectors/injeção até a biblioteca atualizar. O disclaimer do próprio projeto é direto: o WhatsApp não permite bots nem clientes não oficiais; bloqueio não é garantidamente evitável.
A WhatsApp Business Platform Cloud API é a mensageria hospedada pela Meta para uso comercial verificado. Você envia via Graph API e recebe via webhooks: a Meta faz POST JSON quando usuários te mandam mensagem e quando mensagens de saída mudam de status (sent, delivered, read, failed). A documentação trata webhooks como peça central: conteúdo de entrada e status de entrega chegam por esse canal.
Esse caminho tem templates, quality rating, limites de throughput e pricing — mas é o desenhado para automação comercial em conformidade.
| Preocupação | Baileys | whatsapp-web.js | Cloud API |
|---|---|---|---|
| Transporte | Cliente de protocolo WebSocket | Chromium headless + WhatsApp Web | Graph API HTTPS + webhooks |
| Custo de recurso | Relativamente leve | Pesado (browser por sessão) | Só seus workers HTTP |
| Sensação de API | Mapa de eventos em sock.ev, mais baixo nível | Eventos de alto nível no Client | Payloads de webhook + envio REST |
| Grupos / features pessoais ricas | Forte (não oficial) | Forte (não oficial) | Conjunto de produto business |
| Estabilidade sob mudanças do WA | Drift de protocolo / crypto | Quebra de UI + injeção no Store | Docs versionadas da plataforma |
| Risco de ToS / ban | Alto para automação | Alto para automação | Pensado para business quando as políticas são seguidas |
| Melhor encaixe | Estudo, tooling pessoal, sessões MD leves | Protótipos com API simples | Mensageria de cliente em produção |
Nenhuma biblioteca não oficial é “mais segura” em termos de política. Baileys costuma ser mais barata de rodar; whatsapp-web.js costuma ser mais fácil de começar. Cloud API é o caminho de produto durável.
Esses padrões valem se o emissor for sock.ev, Client ou um router de webhook. Roube a forma; troque o adapter.
Não espalhe WAMessage do Baileys ou Message do wwebjs pela aplicação. Mapeie uma vez para seus eventos:
// Evento de domínio — independente da biblioteca
type InboundMessage = {
id: string
chatId: string
fromMe: boolean
text?: string
receivedAt: Date
raw?: unknown // opcional, só para debug
}
function fromBaileys(msg: {
key: { id?: string | null; remoteJid?: string | null; fromMe?: boolean | null }
message?: { conversation?: string | null; extendedTextMessage?: { text?: string | null } | null } | null
}): InboundMessage | null {
if (!msg.key.id || !msg.key.remoteJid) return null
const text =
msg.message?.conversation ??
msg.message?.extendedTextMessage?.text ??
undefined
return {
id: msg.key.id,
chatId: msg.key.remoteJid,
fromMe: Boolean(msg.key.fromMe),
text,
receivedAt: new Date(),
}
}Handlers assinam inbound.message, não os internos da biblioteca. Trocar Baileys por Cloud API depois vira mudança de adapter, não reescrita das regras de negócio.
Runtimes de socket e webhook punem listeners lentos. Baileys pode agrupar com sock.ev.process; a Cloud API espera um 200 HTTP a tempo para a Meta não retentar com agressividade. Padrão:
SKIP LOCKED).message.processed / message.failed.Se você await chamadas de LLM, CRM ou download de imagem dentro do handler cru de messages.upsert / message, um reconnect ou crash perde trabalho em voo e o sistema parece “instável” sob carga.
Trate o ciclo de vida da sessão como evento de produto, não como console.log:
connection.update (connecting | open | close, qr opcional), creds.update (precisa persistir).qr, authenticated, ready, auth_failure, disconnected.Regra operacional das docs do Baileys e da comunidade: em close, reconecte a menos que o motivo seja logged out. Loop cego de reconnect depois de ban ou logout queima CPU e parece abuso.
Streams ligados ao WhatsApp são at-least-once na prática: webhooks retentam; sync de histórico pode repetir; o processo pode crashar depois do efeito colateral e antes do ack. Desenhe para:
O messages.upsert do Baileys traz type: 'notify' para tráfego ao vivo vs 'append' para histórico — filtre para um sync não virar blast de boas-vindas.
Separe intenção de resultado:
Command: SendText { chatId, body, correlationId }
Event: MessageAccepted { providerMessageId, correlationId }
Event: MessageDelivered { providerMessageId }
Event: MessageRead { providerMessageId }
Event: MessageFailed { providerMessageId, error }Baileys expõe updates de status via messages.update / eventos de receipt; whatsapp-web.js tem eventos relacionados a ack; na Cloud API o status mora no mesmo campo messages do webhook de entrada. A lógica de “ticket resolvido” no CRM deve depender de eventos de recibo, não do instante em que você chamou sendMessage.
Forma ilustrativa alinhada à docs de eventos do Baileys. Ajuste imports/pacote à versão que você fixar; a v7 trouxe breaking changes.
import makeWASocket, {
DisconnectReason,
useMultiFileAuthState,
} from 'baileys'
import { Boom } from '@hapi/boom'
async function start() {
// Helper de demo — a docs do Baileys avisa que useMultiFileAuthState é ineficiente em produção.
const { state, saveCreds } = await useMultiFileAuthState('./auth')
const sock = makeWASocket({ auth: state })
sock.ev.on('creds.update', saveCreds)
sock.ev.on('connection.update', (update) => {
const { connection, lastDisconnect, qr } = update
if (qr) {
// Renderizar QR para parear em Dispositivos vinculados
console.log('scan qr')
}
if (connection === 'close') {
const code = (lastDisconnect?.error as Boom | undefined)?.output?.statusCode
const shouldReconnect = code !== DisconnectReason.loggedOut
if (shouldReconnect) start()
}
})
sock.ev.on('messages.upsert', async ({ messages, type }) => {
if (type !== 'notify') return
for (const msg of messages) {
if (msg.key.fromMe || !msg.message) continue
// Padrão 2: enqueue(fromBaileys(msg)) em vez de trabalho pesado aqui
const jid = msg.key.remoteJid
if (!jid) continue
await sock.sendMessage(jid, { text: 'Recebi — enfileirei.' })
}
})
}
start()Padrão das docs oficiais:
const { Client, LocalAuth } = require('whatsapp-web.js')
const qrcode = require('qrcode-terminal')
const client = new Client({
authStrategy: new LocalAuth(),
puppeteer: {
headless: true,
args: ['--no-sandbox'], // comum em containers; entenda o trade-off de segurança
},
})
client.on('qr', (qr) => {
qrcode.generate(qr, { small: true })
})
client.on('ready', () => {
console.log('Client is ready!')
})
client.on('message', async (msg) => {
// Padrão 2: enfileirar evento de domínio; responder no worker quando possível
if (msg.body === '!ping') {
await msg.reply('pong')
}
})
client.initialize()Mesmo conselho de arquitetura: listener fino; persistir sessão com auth strategy; monitorar disconnected como evento de pager.
Sessões de WhatsApp são longevas, perdem pacotes sob flap de rede e são tagarelas (recibos, presença, metadados de grupo). Um script request/response assume happy path. Um desenho event-driven assume:
Por isso Baileys documenta sock.ev.process para handling em lote, a Cloud API é centrada em webhooks, e o EventEmitter do Node aparece na base das duas stacks não oficiais.
Prefira Cloud API quando:
Considere Baileys quando:
Considere whatsapp-web.js quando:
Client acelera mais o time do que o mapa de eventos do Baileys.Não use clientes não oficiais quando:
O aviso sobre automação não autorizada existe exatamente porque tooling de terceiros de bulk/auto continua tentando contornar a Business Platform.
creds.update (Baileys) ou via auth strategy suportada (wwebjs).await pesado dentro de callbacks crus de socket/webhook.append.São não oficiais. Baileys declara que não é afiliado ao WhatsApp e desencoraja bulk/automação. whatsapp-web.js declara que o WhatsApp não permite bots nem clientes não oficiais e que o risco de bloqueio permanece. Os Termos e a Central de Ajuda proíbem auto/bulk não autorizado na plataforma de consumo. Automação comercial permitida passa pelos produtos oficiais de business.
Se quiser entender eventos e o modelo de socket multi-device, comece pelo mapa sock.ev do Baileys. Se quiser o ping-pong mais rápido, o Client do whatsapp-web.js é mais amigável — depois refatore para filas antes de colocar lógica real.
A docs do Baileys diz que pode vincular conta do app pessoal ou Business via Dispositivos vinculados. Isso ainda não é o produto Cloud API / WABA. Vincular não concede direitos de API oficial nem cobertura de ToS para automação.
De forma limpa: a Meta é o emissor; seu endpoint HTTPS é o adapter; os padrões 1–5 continuam valendo. Assine o campo messages para entrada e status de saída, verifique assinaturas, responda rápido e processe de forma assíncrona.
As mesmas ideias de EDA se aplicam. No ecossistema Go, whatsmeow é uma biblioteca multi-device citada com frequência (por exemplo em bridges Matrix). Também é não oficial — risco de política não some com a linguagem.
Design event-driven não é cola opcional para automações de WhatsApp — é a arquitetura. Baileys entrega um mapa de eventos tipado em WebSocket sem browser. whatsapp-web.js entrega conveniência via Puppeteer com custo maior de recurso. Ambos são poderosos para aprendizado e tooling pessoal cuidadoso, e ambos ficam do lado errado das regras do WhatsApp para automação não autorizada.
Implemente os cinco padrões — eventos de domínio, ack rápido + fila, eventos de ciclo de vida, idempotência e separação command/recibo — e mantenha o caminho oficial de webhooks da Cloud API como destino de qualquer coisa que precise sobreviver ao contato com clientes reais e compliance real.
Quer me ajudar a melhorar o site?