# Baileys WebSocket Events: A Complete Guide to Connection Handling

> Discover Baileys WebSocket events like connection update and message upsert. Learn to handle these high level ev emitter events for robust connection management in your Baileys applications.

- Repository: [WhiskeySockets/Baileys](https://github.com/WhiskeySockets/Baileys)
- Tags: how-to-guide
- Published: 2026-08-01

---

**Baileys emits WebSocket events through a `WebSocketClient` wrapper in [`src/Socket/socket.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/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`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/socket.ts), your code should interact exclusively with the public event emitter interface defined in [`src/Types/Events.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/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`](https://github.com/WhiskeySockets/Baileys/blob/main/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

```typescript
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.

```typescript
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/main/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/main/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/main/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/main/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/main/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`](https://github.com/WhiskeySockets/Baileys/blob/main/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`](https://github.com/WhiskeySockets/Baileys/blob/main/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`](https://github.com/WhiskeySockets/Baileys/blob/main/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`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/reporting-utils.ts) file converts server errors into `DisconnectReason` codes you can use for retry decisions.