This page is also available in English.

Voltar aos artigos

5 padrões event-driven para automação no WhatsApp (Baileys vs whatsapp-web.js)

Publicado em 11 min de leitura

  • WhatsApp
  • Event-Driven Architecture
  • Baileys
  • Node.js
  • Automation
Mão segurando um smartphone com a interface de um app de mensagens

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.

O que significa “automação event-driven no WhatsApp”

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:

CamadaO que emiteO que você escuta
Baileyssock.ev (BaileysEventMap tipado)messages.upsert, connection.update, creds.update, …
whatsapp-web.jsClient (EventEmitter)message, qr, ready, disconnected, …
Cloud APIWebhooks HTTPS da Metacampo 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).

Como as três stacks funcionam de verdade

Baileys — WebSocket, sem browser

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:

  1. Persistir o auth state (creds.update precisa ser salvo — a docs avisa que pular isso quebra auth e descriptografia).
  2. Criar o socket com makeWASocket.
  3. Assinar eventos em sock.ev.
  4. Tratar disconnect como evento: reconectar a menos que o motivo seja loggedOut.

whatsapp-web.js — Puppeteer sobre o WhatsApp Web

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.

Cloud API — webhooks oficiais

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.

Baileys vs whatsapp-web.js vs Cloud API

PreocupaçãoBaileyswhatsapp-web.jsCloud API
TransporteCliente de protocolo WebSocketChromium headless + WhatsApp WebGraph API HTTPS + webhooks
Custo de recursoRelativamente levePesado (browser por sessão)Só seus workers HTTP
Sensação de APIMapa de eventos em sock.ev, mais baixo nívelEventos de alto nível no ClientPayloads de webhook + envio REST
Grupos / features pessoais ricasForte (não oficial)Forte (não oficial)Conjunto de produto business
Estabilidade sob mudanças do WADrift de protocolo / cryptoQuebra de UI + injeção no StoreDocs versionadas da plataforma
Risco de ToS / banAlto para automaçãoAlto para automaçãoPensado para business quando as políticas são seguidas
Melhor encaixeEstudo, tooling pessoal, sessões MD levesProtótipos com API simplesMensageria 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.

5 padrões event-driven que aguentam o tranco

Esses padrões valem se o emissor for sock.ev, Client ou um router de webhook. Roube a forma; troque o adapter.

1. Adapters finos, eventos de domínio gordos

Não espalhe WAMessage do Baileys ou Message do wwebjs pela aplicação. Mapeie uma vez para seus eventos:

TypeScript
// 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.

2. Acknowledge rápido, processar assíncrono

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:

  1. Validar + persistir uma linha de inbox (idempotente no id da mensagem).
  2. Enfileirar o trabalho (Redis, SQS, BullMQ, Postgres SKIP LOCKED).
  3. Retornar / terminar o listener rápido.
  4. Workers emitem 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.

3. Conexão e auth como eventos de primeira classe

Trate o ciclo de vida da sessão como evento de produto, não como console.log:

  • Baileys: connection.update (connecting | open | close, qr opcional), creds.update (precisa persistir).
  • whatsapp-web.js: qr, authenticated, ready, auth_failure, disconnected.
  • Cloud API: quality do número, alertas de conta, status de templates.

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.

4. Idempotência e hipóteses de ordenação

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:

  • Deduplicar por id da mensagem (e id de status para recibos).
  • Nunca assumir ordem total entre chats.
  • Tornar respostas seguras para retry (ou guardar marcadores de “já respondi”).

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.

5. Outbound como commands, recibos como events

Separe intenção de resultado:

Text
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.

Exemplo prático: esqueleto Baileys

Forma ilustrativa alinhada à docs de eventos do Baileys. Ajuste imports/pacote à versão que você fixar; a v7 trouxe breaking changes.

TypeScript
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()

Exemplo prático: esqueleto whatsapp-web.js

Padrão das docs oficiais:

JavaScript
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.

Por que design event-driven importa aqui

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:

  • O socket vai morrer no meio do handler.
  • O mesmo id de mensagem pode aparecer duas vezes.
  • A lógica de negócio vai crescer (tags, roteamento, handoff humano) sem reescrever o transporte.

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.

Quando usar cada abordagem

Prefira Cloud API quando:

  • Você mensagem clientes como empresa.
  • Precisa de política previsível, templates e canais de suporte.
  • Não pode arriscar o número principal da empresa.

Considere Baileys quando:

  • Está estudando o protocolo multi-device ou fazendo tooling pessoal em número descartável.
  • Quer um processo Node sem browser e topa acompanhar churn de protocolo/biblioteca (incluindo migrações v7).

Considere whatsapp-web.js quando:

  • Quer um protótipo rápido e aceita o footprint do Chromium.
  • A API de alto nível do Client acelera mais o time do que o mapa de eventos do Baileys.

Não use clientes não oficiais quando:

  • O plano é outreach em massa, listas raspadas ou “blast” de marketing.
  • Você precisa de uptime contratual ou evidência de compliance.
  • Perder o número dói financeiramente ou juridicamente.

O aviso sobre automação não autorizada existe exatamente porque tooling de terceiros de bulk/auto continua tentando contornar a Business Platform.

Boas práticas

  1. Fixe versões de Baileys / whatsapp-web.js; atualize de propósito depois de ler o release notes.
  2. Persista credenciais em todo creds.update (Baileys) ou via auth strategy suportada (wwebjs).
  3. Handlers idempotentes chaveados pelo id da mensagem.
  4. Backoff no reconnect; pare em sinais de logout/ban.
  5. Rate-limit no outbound; imite ritmo humano mesmo em experimentos — envio agressivo é rude e detectável.
  6. Separe segredos: pastas de auth, tokens e números nunca vão para o git.
  7. Observe o pipeline: profundidade de fila, erros de handler, motivos de disconnect, códigos de falha de envio.
  8. Planeje a saída: mantenha eventos de domínio limpos para a Cloud API ser viável depois.

Erros comuns

  • Tratar APIs não oficiais como WhatsApp de produção “de graça”.
  • Fazer await pesado dentro de callbacks crus de socket/webhook.
  • Ignorar eventos de history sync e spammar auto-respostas em append.
  • Não salvar creds do Baileys (falhas misteriosas de decrypt/auth depois).
  • Rodar Puppeteer em Docker sem limite de memória ou flags sensatas — e culpar o WhatsApp por disconnects “aleatórios”.
  • Montar um blaster de marketing e se surpreender com restrições de conta ou ban.
  • Acoplar lógica de CRM aos tipos do Baileys de forma tão profunda que um major da biblioteca vira semanas de rewrite.

Perguntas frequentes

Baileys ou whatsapp-web.js são permitidos pelo WhatsApp?

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.

Qual biblioteca aprender primeiro?

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.

Dá para usar com conta do app WhatsApp Business?

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.

Como os webhooks da Cloud API mapeiam para esses padrões?

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.

E Go ou outras linguagens?

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.

Conclusão

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.

Referências

  1. Introdução ao Baileys — WhiskeySockets (acesso ago 2026)
  2. Eventos do Baileys — mirror da docs WhiskeySockets (acesso ago 2026)
  3. WhiskeySockets/Baileys — repositório no GitHub
  4. Notas de migração Baileys v7 — WhiskeySockets
  5. Documentação whatsapp-web.js — docs do projeto (v1.34.x no momento do acesso)
  6. Guia whatsapp-web.js — como funciona a abordagem com Puppeteer
  7. Node.js EventEmitter — documentação Node.js
  8. Termos de Serviço do WhatsApp — uso aceitável (bulk / auto-messaging)
  9. Uso não autorizado de mensagens automatizadas ou em massa no WhatsApp — Central de Ajuda
  10. Sobre contas restritas — Central de Ajuda
  11. Visão geral da WhatsApp Cloud API — Meta Developer Docs
  12. Configurar webhooks do WhatsApp — Meta Developer Docs
  13. WhatsApp Business Messaging Policy — WhatsApp

Comentários