Skip to content

Improve WhatsApp/Baileys group message reliability #7433

Description

@octopuslabs-fl

Improve WhatsApp/Baileys group message reliability

Summary

The current WhatsApp/Baileys integration has a solid foundation but is missing several recommended patterns from the Baileys documentation that improve group message reliability, especially for high-volume groups and message retry scenarios.

Current Implementation ✓

The existing implementation already includes several best practices:

Feature Status
@whiskeysockets/baileys v7.0.0-rc.9
makeCacheableSignalKeyStore
Manual group metadata caching
Processes all messages in array
LID resolution

Missing Recommendations

1. getMessage callback (HIGH PRIORITY)

Impact: Required for message retry/decryption. Without this, the client cannot fulfill retry requests from WhatsApp servers, leading to failed message decryption in groups.

// Recommended implementation
const getMessage = async (key: WAMessageKey): Promise<WAMessageContent | undefined> => {
  // Check your message store/database
  const msg = await messageStore.get(key.remoteJid!, key.id!)
  return msg?.message || undefined
}

const sock = makeWASocket({
  // ... other options
  getMessage,
})

2. cachedGroupMetadata socket option

Impact: The manual cache exists but isn't passed to the socket constructor. Baileys can use this for automatic group metadata resolution during message processing.

// Current: Manual cache exists but not connected to socket
// Recommended: Pass cache to socket constructor

const groupMetadataCache = new Map<string, GroupMetadata>()

const sock = makeWASocket({
  // ... other options
  cachedGroupMetadata: async (jid) => {
    return groupMetadataCache.get(jid)
  },
})

3. groups.upsert / groups.update events

Impact: Cache only populates on-demand. Proactive cache updates prevent stale data and reduce API calls.

// Listen for group events to keep cache fresh
sock.ev.on('groups.upsert', (groups) => {
  for (const group of groups) {
    groupMetadataCache.set(group.id, group)
    logger.debug(`Group cached: ${group.subject}`)
  }
})

sock.ev.on('groups.update', (updates) => {
  for (const update of updates) {
    const existing = groupMetadataCache.get(update.id!)
    if (existing) {
      groupMetadataCache.set(update.id!, { ...existing, ...update })
      logger.debug(`Group updated: ${update.id}`)
    }
  }
})

// Also handle participant updates
sock.ev.on('group-participants.update', async ({ id, participants, action }) => {
  const cached = groupMetadataCache.get(id)
  if (cached) {
    // Update participant list based on action (add/remove/promote/demote)
    // ... update logic
  }
})

4. lid-mapping.update event

Impact: New LID (Linked ID) mappings aren't persisted, causing repeated lookups and potential resolution failures.

// Persist LID mappings for consistent identity resolution
const lidMap = new Map<string, string>() // LID -> JID

sock.ev.on('lid-mapping.update', (mapping) => {
  for (const [lid, jid] of Object.entries(mapping)) {
    lidMap.set(lid, jid)
    logger.debug(`LID mapping: ${lid} -> ${jid}`)
  }
  // Optionally persist to database for cross-session continuity
})

// Helper to resolve LID to JID
function resolveJid(identifier: string): string {
  if (identifier.includes('@lid')) {
    return lidMap.get(identifier) || identifier
  }
  return identifier
}

Complete Recommended Pattern

Here's a consolidated example combining all recommendations:

import makeWASocket, {
  DisconnectReason,
  makeCacheableSignalKeyStore,
  WAMessageKey,
  GroupMetadata,
} from '@whiskeysockets/baileys'

// Caches
const groupMetadataCache = new Map<string, GroupMetadata>()
const messageStore = new Map<string, WAMessage>() // Or use database
const lidMap = new Map<string, string>()

// getMessage callback for retry handling
const getMessage = async (key: WAMessageKey) => {
  const storeKey = `${key.remoteJid}:${key.id}`
  const msg = messageStore.get(storeKey)
  return msg?.message || undefined
}

const sock = makeWASocket({
  auth: state,
  printQRInTerminal: true,
  
  // Pass getMessage for retry support
  getMessage,
  
  // Connect group cache to socket
  cachedGroupMetadata: async (jid) => groupMetadataCache.get(jid),
  
  // Use cacheable signal key store
  keys: makeCacheableSignalKeyStore(state.keys, logger),
})

// Store messages for retry support
sock.ev.on('messages.upsert', ({ messages }) => {
  for (const msg of messages) {
    if (msg.key.id && msg.key.remoteJid) {
      messageStore.set(`${msg.key.remoteJid}:${msg.key.id}`, msg)
    }
  }
})

// Proactive group cache updates
sock.ev.on('groups.upsert', (groups) => {
  for (const group of groups) {
    groupMetadataCache.set(group.id, group)
  }
})

sock.ev.on('groups.update', (updates) => {
  for (const update of updates) {
    const existing = groupMetadataCache.get(update.id!)
    if (existing) {
      groupMetadataCache.set(update.id!, { ...existing, ...update })
    }
  }
})

// Persist LID mappings
sock.ev.on('lid-mapping.update', (mapping) => {
  for (const [lid, jid] of Object.entries(mapping)) {
    lidMap.set(lid, jid)
  }
})

Expected Benefits

  1. Improved message decryption - getMessage callback enables proper retry handling
  2. Faster group operations - Socket-connected cache reduces API calls
  3. Fresher group data - Proactive updates prevent stale metadata
  4. Consistent identity resolution - Persisted LID mappings work across sessions

References

Environment

  • Baileys version: @whiskeysockets/baileys v7.0.0-rc.9
  • Node.js: v24.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.enhancementNew feature or requestimpact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions