How Baileys Manages Presence Updates: Typing Indicators and Online Status Explained

Baileys handles WhatsApp presence updates through a dedicated presence WebSocket stanza that flows through raw node capture, parsing, and event emission, exposing strongly-typed presence.update events for developers to consume.

Baileys, the popular open-source WhatsApp Web API from WhiskeySockets/Baileys, provides robust support for tracking contact presence—including typing indicators, online status, and offline states. Understanding how this system works is essential for building responsive chat applications that react to user activity in real time.

The Three-Stage Presence Flow

Baileys processes presence information through a clean separation of concerns: raw protocol handling, data normalization, and public event emission.

Stage 1: Raw WebSocket Capture

Incoming presence data arrives as presence stanzas over the WebSocket connection. The core socket module registers a dedicated listener to intercept these nodes before any processing occurs.

In src/Socket/socket.ts around line 1339, the WebSocket event handler attaches a callback specifically for presence nodes:

// From src/Socket/socket.ts
ws.on('CB:presence', handlePresenceUpdate)

This registration ensures all presence-related traffic from WhatsApp's servers is immediately captured and routed for parsing.

Stage 2: Parsing and Data Normalization

The heavy lifting happens in src/Socket/chats.ts between lines 855–906. Here, Baileys extracts the presence type and constructs a structured data object.

The parser handles multiple presence states:

  • available — user is online
  • unavailable — user is offline
  • composing — user is typing
  • paused — user stopped typing

If a malformed node arrives, the implementation logs the error and silently discards it rather than crashing the connection. This defensive approach maintains socket stability even with protocol edge cases.

The handler builds a PresenceData object containing lastKnownPresence and related metadata, then prepares it for public consumption.

Stage 3: Event Emission

After successful parsing, Baileys emits the presence.update event via the internal event emitter at line 906 in src/Socket/chats.ts:

// From src/Socket/chats.ts
ev.emit('presence.update', { id, presences })

This emission transforms internal protocol data into a developer-friendly, strongly-typed event.

Event Type Definition

The public API contract is defined in src/Types/Events.ts at lines 57–58:

// From src/Types/Events.ts
'presence.update': { 
  id: string; 
  presences: { [participant: string]: PresenceData } 
}

The PresenceData type (defined in src/Types/Message.ts) includes:

  • lastKnownPresence: the core presence state
  • isOnline: boolean convenience flag
  • Timestamp fields for tracking state changes

Subscribing to Presence Updates

Developers interact with presence functionality through two primary patterns: reactive event listeners and proactive subscription requests.

Listening for Presence Changes

import makeWASocket, { useSingleFileAuthState } from '@adiwajshing/baileys'

const { state } = useSingleFileAuthState('./auth_info.json')
const sock = makeWASocket({
  auth: state,
  printQRInTerminal: true,
})

// React to typing and online status changes
sock.ev.on('presence.update', ({ id, presences }) => {
  for (const [participant, presence] of Object.entries(presences)) {
    switch (presence.lastKnownPresence) {
      case 'composing':
        console.log(`${participant} is typing...`)
        break
      case 'available':
        console.log(`${participant} is online`)
        break
      case 'unavailable':
        console.log(`${participant} is offline`)
        break
      case 'paused':
        console.log(`${participant} stopped typing`)
        break
    }
  }
})

Requesting Presence for Specific Contacts

By default, WhatsApp does not broadcast presence for all contacts. You must explicitly subscribe using presenceSubscribe():

// Request presence updates for a specific JID
await sock.presenceSubscribe('1234567890@s.whatsapp.net')

// Group subscriptions work similarly
await sock.presenceSubscribe('1234567890-1234567890@g.us')

According to the Baileys source code, presenceSubscribe is implemented in src/Socket/chats.ts and sends a protocol-level subscription request to WhatsApp's servers.

Design Rationale

The Baileys architecture for presence updates emphasizes three qualities:

  • Separation of concerns — Protocol parsing remains isolated in chats.ts while the public API exposes clean events
  • Fault tolerance — Malformed nodes trigger logged errors without disrupting the WebSocket connection
  • Extensibility — New presence types require only parser updates, not structural changes

Summary

  • Baileys captures raw presence stanzas in src/Socket/socket.ts via the CB:presence WebSocket event
  • src/Socket/chats.ts parses nodes between lines 855–906, creating PresenceData objects
  • Clean presence.update events are emitted at line 906, with types declared in src/Types/Events.ts
  • Developers use sock.ev.on('presence.update', …) to react to changes and sock.presenceSubscribe(jid) to request updates

Frequently Asked Questions

How do I know when someone is typing in Baileys?

Listen for the presence.update event and check presence.lastKnownPresence === 'composing'. This state indicates the contact is actively typing. The 'paused' state fires when typing stops without sending a message.

Why am I not receiving presence updates for some contacts?

WhatsApp requires explicit subscriptions for presence data. Call await sock.presenceSubscribe(jid) for each contact or group you want to monitor. Without this step, the WhatsApp server will not push presence information to your socket.

What presence states does Baileys support?

According to the parser implementation in src/Socket/chats.ts, Baileys handles: available (online), unavailable (offline), composing (typing), and paused (stopped typing). These map directly to WhatsApp's protocol-level presence indicators.

Where is the PresenceData type defined?

The PresenceData interface is located in src/Types/Message.ts. It includes lastKnownPresence as a string literal union, boolean flags, and optional timestamp fields that provide complete status context for each contact.

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 →