Skip to main content
Baileys provides a powerful event system for intercepting and handling WebSocket frames before they’re processed by the main event handlers.

Event Callback System

The WebSocket client emits events using a special callback prefix system that allows pattern matching against binary node properties.

Callback Prefixes

All WebSocket callback events use the prefix CB: followed by a pattern:
import type { BinaryNode } from '@whiskeysockets/baileys'

// Pattern: CB:{tag}
sock.ws.on('CB:message', (node: BinaryNode) => {
    console.log('Any message node received')
})
From src/Defaults/index.ts:14:
export const DEF_CALLBACK_PREFIX = 'CB:'
export const DEF_TAG_PREFIX = 'TAG:'

Event Pattern Matching

The event system supports hierarchical pattern matching based on the binary node structure.

Basic Tag Matching

Match any message with a specific tag:
// Listen for all 'iq' nodes
sock.ws.on('CB:iq', (node: BinaryNode) => {
    console.log('IQ node:', node)
})

// Listen for all 'message' nodes
sock.ws.on('CB:message', (node: BinaryNode) => {
    console.log('Message node:', node)
})

// Listen for all 'presence' nodes
sock.ws.on('CB:presence', (node: BinaryNode) => {
    console.log('Presence update:', node)
})

Attribute Matching

Match nodes with specific attribute values:
// Pattern: CB:{tag},{attr}:{value}
sock.ws.on('CB:iq,type:get', (node: BinaryNode) => {
    console.log('IQ get request:', node)
})

sock.ws.on('CB:iq,type:set', (node: BinaryNode) => {
    console.log('IQ set request:', node)
})

sock.ws.on('CB:iq,type:result', (node: BinaryNode) => {
    console.log('IQ result:', node)
})

ID-Based Matching

Match messages by their unique ID attribute:
// Pattern: CB:{tag},id:{messageId}
const msgId = 'abcd1234'
sock.ws.on(`CB:iq,id:${msgId}`, (node: BinaryNode) => {
    console.log('Specific message response:', node)
})

Content Tag Matching

Match based on the first child node’s tag:
// Pattern: CB:{tag},,{contentTag}
sock.ws.on('CB:ib,,edge_routing', (node: BinaryNode) => {
    console.log('Edge routing message:', node)
})

sock.ws.on('CB:iq,,pair-success', (node: BinaryNode) => {
    console.log('Pairing successful:', node)
})

sock.ws.on('CB:notification,,encrypt', (node: BinaryNode) => {
    console.log('Encryption notification:', node)
})

Combined Matching

Combine tag, attributes, and content matching:
// Pattern: CB:{tag},{attr}:{value},{contentTag}
sock.ws.on('CB:iq,type:set,pair-device', (node: BinaryNode) => {
    console.log('Pair device request:', node)
})

sock.ws.on('CB:ib,from:@s.whatsapp.net,edge_routing', (node: BinaryNode) => {
    console.log('Server edge routing:', node)
})

How Event Matching Works

From src/Socket/socket.ts:583-620, the event matching logic:
const onMessageReceived = async (data: Buffer) => {
    await noise.decodeFrame(data, frame => {
        // Binary node processing
        if (!(frame instanceof Uint8Array)) {
            const msgId = frame.attrs.id
            
            // Emit tag-specific event with ID
            ws.emit(`TAG:${msgId}`, frame)
            
            // Extract matching components
            const l0 = frame.tag
            const l1 = frame.attrs || {}
            const l2 = Array.isArray(frame.content) ? frame.content[0]?.tag : ''
            
            // Emit hierarchical callback events
            for (const key of Object.keys(l1)) {
                // CB:tag,attr:value,content
                ws.emit(`CB:${l0},${key}:${l1[key]},${l2}`, frame)
                // CB:tag,attr:value
                ws.emit(`CB:${l0},${key}:${l1[key]}`, frame)
                // CB:tag,attr
                ws.emit(`CB:${l0},${key}`, frame)
            }
            
            // CB:tag,,content
            ws.emit(`CB:${l0},,${l2}`, frame)
            // CB:tag
            ws.emit(`CB:${l0}`, frame)
        }
    })
}

Common Event Patterns

Connection Events

// Stream ended by server
sock.ws.on('CB:xmlstreamend', () => {
    console.log('Connection terminated by server')
})

// Stream errors
sock.ws.on('CB:stream:error', (node: BinaryNode) => {
    console.error('Stream error:', node)
})

// Connection success
sock.ws.on('CB:success', (node: BinaryNode) => {
    console.log('Login successful')
})

// Connection failure
sock.ws.on('CB:failure', (node: BinaryNode) => {
    console.error('Connection failed:', node.attrs)
})

Pairing & Authentication

// QR code pairing
sock.ws.on('CB:iq,type:set,pair-device', (node: BinaryNode) => {
    console.log('QR code pairing initiated')
})

// Pairing success
sock.ws.on('CB:iq,,pair-success', (node: BinaryNode) => {
    console.log('Device paired successfully')
})

Message Synchronization

// Offline message batch
sock.ws.on('CB:ib,,offline_preview', (node: BinaryNode) => {
    console.log('Offline preview received')
})

// All offline messages processed
sock.ws.on('CB:ib,,offline', (node: BinaryNode) => {
    const count = node.content?.[0]?.attrs?.count || 0
    console.log(`Processed ${count} offline messages`)
})

Device & Session Management

// Edge routing updates
sock.ws.on('CB:ib,,edge_routing', (node: BinaryNode) => {
    console.log('Edge routing info updated')
})

// Multi-device downgrade
sock.ws.on('CB:ib,,downgrade_webclient', () => {
    console.error('Multi-device beta not joined')
})

Tag-Based Events

Besides callback events, you can listen for tag-based responses:
// Wait for a specific message ID response
const messageId = 'unique-msg-id'

sock.ws.on(`TAG:${messageId}`, (response: BinaryNode) => {
    console.log('Got response for message:', response)
})

// Send a message that expects a response
await sock.sendNode({
    tag: 'iq',
    attrs: {
        id: messageId,
        type: 'get',
        xmlns: 'w:profile:picture',
        to: 'some-jid'
    }
})
Baileys internally uses TAG: events with the waitForMessage() function to implement query/response patterns.

Practical Examples

Battery Monitoring

sock.ws.on('CB:notification', (node: BinaryNode) => {
    if (Array.isArray(node.content)) {
        const batteryNode = node.content.find(
            n => typeof n === 'object' && n.tag === 'battery'
        )
        
        if (batteryNode && typeof batteryNode === 'object') {
            console.log('Battery level:', batteryNode.attrs?.value)
            console.log('Charging:', batteryNode.attrs?.live === '1')
        }
    }
})

Presence Tracking

sock.ws.on('CB:presence', (node: BinaryNode) => {
    const from = node.attrs?.from
    const type = node.attrs?.type
    
    if (type === 'available') {
        console.log(`${from} is online`)
    } else if (type === 'unavailable') {
        console.log(`${from} is offline`)
    } else if (type === 'composing') {
        console.log(`${from} is typing`)
    }
})

Group Metadata Changes

sock.ws.on('CB:notification,type:group_update', (node: BinaryNode) => {
    const groupJid = node.attrs?.from
    
    if (Array.isArray(node.content)) {
        for (const child of node.content) {
            if (typeof child === 'object') {
                if (child.tag === 'subject') {
                    console.log(`Group ${groupJid} name changed to:`, child.content)
                } else if (child.tag === 'description') {
                    console.log(`Group ${groupJid} description changed`)
                }
            }
        }
    }
})

Call Events

sock.ws.on('CB:call', (node: BinaryNode) => {
    const callId = node.attrs?.id
    const from = node.attrs?.from
    
    console.log(`Incoming call from ${from}, ID: ${callId}`)
    
    // Auto-reject calls
    await sock.rejectCall(callId, from)
})

Event Handler Best Practices

Avoid Memory Leaks: Always clean up event listeners when they’re no longer needed.
// Good: Remove listener when done
const handler = (node: BinaryNode) => {
    console.log('Received:', node)
    // Remove after first call if one-time
    sock.ws.off('CB:iq', handler)
}
sock.ws.on('CB:iq', handler)

// Or use once() if available
sock.ws.once('CB:iq', (node: BinaryNode) => {
    console.log('Received once:', node)
})
Error Handling: Wrap callback logic in try-catch to prevent unhandled errors from crashing your application.
sock.ws.on('CB:message', async (node: BinaryNode) => {
    try {
        await processMessage(node)
    } catch (error) {
        console.error('Error processing message:', error)
    }
})

Debugging Event Matching

To see which events are triggered, enable debug logging:
import P from 'pino'

const sock = makeWASocket({
    logger: P({ level: 'debug' })
})
Unhandled messages are logged:
{
    "unhandled": true,
    "msgId": "some-id",
    "fromMe": false,
    "frame": { ... },
    "msg": "communication recv"
}

Complete Example

import makeWASocket, { useMultiFileAuthState } from '@whiskeysockets/baileys'
import type { BinaryNode } from '@whiskeysockets/baileys'
import P from 'pino'

async function start() {
    const { state, saveCreds } = await useMultiFileAuthState('auth')
    
    const sock = makeWASocket({
        auth: state,
        logger: P({ level: 'debug' }),
        printQRInTerminal: true
    })
    
    // Connection lifecycle
    sock.ws.on('CB:success', (node: BinaryNode) => {
        console.log('✓ Connected successfully')
    })
    
    sock.ws.on('CB:failure', (node: BinaryNode) => {
        console.error('✗ Connection failed:', node.attrs?.reason)
    })
    
    sock.ws.on('CB:xmlstreamend', () => {
        console.log('✗ Stream ended by server')
    })
    
    // Message events
    sock.ws.on('CB:message', (node: BinaryNode) => {
        console.log('📨 Message received')
    })
    
    // Presence tracking
    sock.ws.on('CB:presence', (node: BinaryNode) => {
        console.log('👤 Presence update:', node.attrs)
    })
    
    // Offline messages
    sock.ws.on('CB:ib,,offline', (node: BinaryNode) => {
        console.log('📬 Offline messages synced')
    })
    
    sock.ev.on('creds.update', saveCreds)
}

start()

Next Steps

Build docs developers (and LLMs) love