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
WebSocketClientinsrc/Socket/socket.tshandles them internally. - Subscribe to
sock.evfor all application logic; this is the stable, documented API surface. - Persist
creds.updateimmediately to maintain sessions across restarts. - Render QR codes from
connection.update.qrrather than parsingCB:iq,type:set,pair-devicedirectly. - Use
sock.logout()orsock.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →