Baileys WebSocket Events: A Complete Guide to Connection Handling

Baileys emits WebSocket events through a WebSocketClient wrapper in src/Socket/socket.ts, automatically translating raw TCP frames into high-level ev emitter events like connection.update, creds.update, and messages.upsert that you should handle instead of low-level socket events.

Baileys WebSocket events power the connection lifecycle between your Node.js application and WhatsApp's binary protocol. While the library internally manages raw socket events in src/Socket/socket.ts, your code should interact exclusively with the public event emitter interface defined in src/Types/Events.ts. This article maps every internal WebSocket event to its public counterpart and shows you exactly how to respond to each.

Complete WebSocket Event Reference

The following table covers every event the WebSocketClient registers, when it triggers, and the correct handling approach.

Event When It Fires How to Handle
open TCP/WebSocket handshake completes (ws.isOpen === true) Start WhatsApp handshake (handled automatically). Attach custom logic before makeSocket() or watch for connection.update → connection: 'open'.
message Raw binary frame arrives from server Do not listen directly. Baileys parses the Noise-encrypted frame and emits higher-level events. Let the library route frames for you.
error Underlying WebSocket reports network or protocol failure Error wrapped in Boom with DisconnectReason, passed to end(). Log for telemetry; avoid retry logic here—wait for connection.update with connection: 'close'.
close Socket closed by server or client Baileys calls end(), emits connection.update with lastDisconnect, cleans up listeners. Use to trigger reconnection or resource cleanup.
CB:xmlstreamend Server signals graceful XML stream termination Treated as normal close; Baileys ends connection.
CB:iq,type:set,pair-device QR-code pairing request from server Baileys extracts pairing ref and emits connection.update with qr field. Render this QR in your UI.
CB:iq,,pair-success Device paired successfully Credentials updated; Baileys emits creds.update and connection.update with isNewLogin: true, then restarts. Persist credentials immediately.
CB:success Login completed; WhatsApp confirms connection Triggers post-login tasks (pre-key upload, passive IQ, digest). Finally emits connection.update with connection: 'open'.
CB:stream:error Server sends protocol-level error stanza Wrapped in Boom with server statusCode, passed to end(). Log and re-authenticate if code indicates credential problem.
CB:failure Generic failure (invalid request, etc.) Connection ends with supplied reason code.
CB:ib,,downgrade_webclient Multi-device beta mismatch Ends with DisconnectReason.multideviceMismatch. Upgrade Baileys or disable multi-device beta.
CB:ib,,offline_preview Server requests offline preview batch Baileys responds with offline_batch node automatically.
CB:ib,,edge_routing Server sends updated routing information Baileys updates creds.routingInfo and emits creds.update. Persist if you store auth state externally.
CB:ib,,offline All offline notifications delivered Buffer flushed; Baileys emits connection.update with receivedPendingNotifications: true. Client is now "ready."
Dynamic callbacks (DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX) Generated per binary node for internal routing Used by Baileys to resolve query() promises and route stanzas. Never attach manual listeners.

Public Event API: What You Actually Listen To

Never attach listeners to raw WebSocket events. Baileys translates all internal events into a stable public API through the ev event emitter.

Core Public Events in src/Types/Events.ts

  • connection.update — Connection state changes: connecting, open, close, plus QR codes and disconnect reasons.
  • creds.update — Authentication credentials changed; persist immediately to maintain session.
  • messages.upsert — New messages arrived (history sync or real-time).
  • messages.update — Message status updates (sent, delivered, read).
  • presence.update — Contact presence changes (available, unavailable, typing).
  • groups.upsert / groups.update — Group metadata changes.

Practical Implementation Pattern

import makeWASocket from '@whiskeySockets/baileys'
import pino from 'pino'

const logger = pino({ level: 'info' })

// Initialize with persisted auth state
const sock = makeWASocket({
  logger,
  auth: { creds, keys },  // Load from your storage
  browser: ['Chrome (Linux)', 'Chrome', '92.0.4515.159']
})

// Handle connection lifecycle
sock.ev.on('connection.update', (update) => {
  const { connection, lastDisconnect, qr, isNewLogin, receivedPendingNotifications } = update
  
  if (qr) {
    // Display QR for pairing
    console.log('Scan this QR:', qr)
  }
  
  if (connection === 'open') {
    console.log('✅ Connected to WhatsApp')
  }
  
  if (connection === 'close') {
    const shouldReconnect = lastDisconnect?.error?.message !== 'logged out'
    console.log('Connection closed. Reconnecting:', shouldReconnect)
    // Implement reconnection logic here
  }
  
  if (isNewLogin) {
    console.log('New login detected')
  }
  
  if (receivedPendingNotifications) {
    console.log('Offline sync complete')
  }
})

// CRITICAL: Persist credentials on every update
sock.ev.on('creds.update', async (newCreds) => {
  await saveCreds(newCreds)  // Your persistence function
  logger.debug('Credentials persisted')
})

// Process incoming messages
sock.ev.on('messages.upsert', ({ messages, type }) => {
  for (const msg of messages) {
    if (type === 'notify') {
      // Real-time message
      console.log(`📩 ${msg.key.remoteJid}: ${msg.message?.conversation}`)
    }
  }
})

Graceful Shutdown Handling

Always use sock.logout() or sock.end() rather than killing the process. This ensures proper cleanup of WebSocket listeners and final state emission.

process.on('SIGINT', async () => {
  await sock.logout()  // Emits final connection.update, clears auth
  process.exit(0)
})

// Or for temporary disconnection (preserves session):
process.on('SIGTERM', async () => {
  await sock.end()     // Emits connection.update with 'close'
  process.exit(0)
})

Key Source Files for Reference

File Purpose
[src/Socket/socket.ts](https://github.com/WhiskeySockets/Baileys/blob/master/src/Socket/socket.ts) Core WebSocketClient implementation; all internal event registration
[src/Types/Events.ts](https://github.com/WhiskeySockets/Baileys/blob/master/src/Types/Events.ts) Public BaileysEventMap definition—your stable API contract
[src/Utils/event-buffer.ts](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/event-buffer.ts) Buffers events during initial offline sync
[src/Socket/Client/websocket.ts](https://github.com/WhiskeySockets/Baileys/blob/master/src/Socket/Client/websocket.ts) Thin WebSocket wrapper with TAG: and CB: routing
[src/Utils/reporting-utils.ts](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/reporting-utils.ts) Error-to-Boom conversion utilities

Summary

  • Never bind to raw WebSocket events—the WebSocketClient in src/Socket/socket.ts handles them internally.
  • Subscribe to sock.ev for all application logic; this is the stable, documented API surface.
  • Persist creds.update immediately to maintain sessions across restarts.
  • Render QR codes from connection.update.qr rather than parsing CB:iq,type:set,pair-device directly.
  • Use sock.logout() or sock.end() for clean shutdowns that properly emit final state.

Frequently Asked Questions

What happens if I listen to raw WebSocket message events?

You receive unparsed binary Noise protocol frames. Baileys decrypts and decodes these in src/Socket/socket.ts, then routes them through the proper channels. Listening directly bypasses encryption handling and breaks the abstraction.

How do I know when the client is fully ready to send messages?

Wait for connection.update with receivedPendingNotifications: true (triggered by CB:ib,,offline). This indicates the offline message buffer in src/Utils/event-buffer.ts has flushed and the sync is complete.

Why does my connection close with multideviceMismatch?

The CB:ib,,downgrade_webclient event fires when WhatsApp's server rejects your client version. Update to the latest Baileys release or explicitly disable multi-device beta in your makeWASocket options.

Can I reconnect automatically after connection: 'close'?

Yes. Check lastDisconnect.error.message in the connection.update payload. Reconnect unless the message equals 'logged out'. The src/Utils/reporting-utils.ts file converts server errors into DisconnectReason codes you can use for retry decisions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →