Skip to content

xhclintohn/Baileys

Repository files navigation

If you find this project helpful, consider following me on GitHub.

toxic-baileys™

npm version License: MIT npm downloads GitHub Node

A professionally enhanced, feature-rich fork of the Baileys WhatsApp Web API. Built for developers who need robust, stable WhatsApp automation with LID identity mapping, AI group support, interoperability, extended message types, and improved connection handling.

Maintainer: 𝐱𝐡_𝐜𝐥𝐢𝐧𝐭𝐨𝐧


Important

LID Mapping — Critical Feature in This Fork

WhatsApp has rolled out Linked Identity (LID) JIDs as part of its cross-platform interoperability initiative. Group messages and status updates now arrive with @lid domain JIDs instead of standard @s.whatsapp.net phone-number JIDs. Without LID resolution, you cannot identify who sent a message in many groups.

This fork ships a complete LIDMappingStore (bidirectional LRU cache + persistent key store) and UsyncLIDProtocol so your bot always knows the real phone number behind every @lid JID. See the LID Mapping System section for full integration details.


Table of Contents


What's New

This update pulls in a batch of fixes and improvements from upstream Baileys and merges them into the existing feature set without touching any of the custom stuff (ToxicHandler, AI groups, interop, LID mapping, group status).

  • LID lookups are faster and less wasteful. The LID mapping store now batches and coalesces concurrent lookups instead of firing off duplicate requests, and it has a proper close() for shutting it down cleanly.
  • A real username API. checkUsername, setUsername, getMyUsername, findUserByUsername, fetchContactUsernames, and a couple more — see Username Management. Also added USyncPictureProtocol and USyncTextStatusProtocol for fetching profile pictures and text statuses via USync directly.
  • New AIRich, Button, ButtonV2, Carousel, and Toolkit builders. See the Rich Message Builders section — chainable APIs for buttons, carousels, and Meta AI-style rich responses with inline citations, LaTeX, code blocks, and tables. Each one also has a couple of shorter alias names if you'd rather import it as something else.
  • sendInteractive (aka inappsignup / inapp_signup). Sends interactive buttons, and automatically falls back to a plain text message if the bot's own paired device is iOS, since buttons don't reliably show up there.
  • Profile pictures no longer crop lopsided. generateProfilePicture was cropping non-square photos from the top-left corner instead of the center — fixed, so the whole subject stays in frame instead of getting cut off to one side.
  • App state sync no longer gives up on the first bad patch. If a single patch fails to decode or a hash check comes back wrong, it logs a warning and keeps syncing instead of throwing and killing the whole sync.
  • Event buffer actually cleans up after itself now. destroy() clears pending timers and listeners when the socket closes, instead of just being a name with nothing behind it.
  • Spoofed protocol messages get dropped. This covers the message-spoofing/app-state-corruption issue reported in GHSA-qvv5-jq5g-4cgg (CVE-2026-48063) — history sync notifications, app state key shares, and placeholder-resend responses that don't actually come from your own account (fromMe) are now rejected instead of processed.
  • Media downloads fall back to the right host. Instead of always assuming mmg.whatsapp.net, downloads now use the host from the message itself when one is present.
  • getChatId fails loudly instead of quietly. Missing remoteJid/participant now throws a clear error instead of silently working with undefined.
  • Profile picture fetching cleaned up to match the newer, simpler query-building approach upstream uses.
  • Group presence updates now carry groupOnlineCount when WhatsApp sends it.
  • Dependency versions and resolutions bumped to match what upstream is currently shipping.

All Previous Features

Compared to upstream Baileys, this fork adds:

Feature Description
LID Mapping System Full LIDMappingStore with LRU cache, persistent key store, bidirectional lookups, and UsyncLIDProtocol
AI Groups makeAIGroupsSocket — create, manage, and receive events for WhatsApp AI-powered groups
Interoperability API makeInteropSocket — fetch third-party integrators, accept Interop TOS, opt in/out
USync Protocol Layer Full WAUSync module: LID, Contact, Device, Disappearing Mode, Status, Text Status, Picture, Username, Bot Profile protocols
MEX / GraphQL Queries executeWMexQuery for structured WhatsApp server queries via the w:mex IQ namespace
me.lid Credential Bot's own LID identity stored in credentials on pairing via configureSuccessfulPairing
lidDbMigrated Login Flag Signals to WhatsApp to push down LID mappings on connect
Group Status V2 ToxicHandler exposed on socket for rich group story/status management
SignalRepositoryWithLIDStore Extended signal repository type that exposes lidMapping directly on the socket
newsletterId() Helper Utility to extract a clean newsletter/channel ID from any JID format

Features

  • Modern & Fast — Latest WA version [2,3000,1035194821], optimised pre-key upload (812 keys)
  • Full LID Identity Resolution — Bidirectional LIDPN mapping with LRU cache, USync lookup, and persistent storage
  • AI Groups Support — Create and manage WhatsApp's AI-powered group type
  • Cross-Platform Interop — Third-party integrator management (BirdyChat, Haiket, and more)
  • Enhanced Stability — Improved connection handling, socket end handlers, ev.destroy() on close, cleaner pre-key retry logic
  • Multi-Device Support — Full WhatsApp multi-device protocol with improved historySyncConfig
  • End-to-End Encryption — Signal Protocol, inlineInitialPayloadInE2EeMsg: true
  • Extended Message Types — Interactive, album, event, poll result, group status, payment, product
  • Advanced Group Management — Group controls, group status V2, communities support
  • Flexible Auth — Multi-file auth state with makeCacheableSignalKeyStore
  • Full Newsletter/Channel API — Follow, create, metadata, newsletterId() helper, fixed join/leave v2 endpoints
  • Developer FriendlytoxicHandler and ToxicHandler exposed on socket, fetchAccountReachoutTimelock, fetchNewChatMessageCap, registerSocketEndHandler, clean API
  • WebSocket ImprovementsperMessageDeflate: false, 100 MB max payload

Installation

Via npm

npm install toxic-baileys

Via Yarn

yarn add toxic-baileys

From GitHub (edge)

npm install github:xhclintohn/Baileys

Drop-in replacement for @whiskeysockets/baileys

{
 "dependencies": {
 "@whiskeysockets/baileys": "npm:toxic-baileys@latest"
 }
}

Quick Start

Basic Connection (QR Code)
import makeWASocket, { useMultiFileAuthState, DisconnectReason } from 'toxic-baileys';
import { Boom } from '@hapi/boom';

async function connectToWhatsApp() {
 const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys');
 const sock = makeWASocket({ auth: state, printQRInTerminal: true });

 sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
 if (connection === 'close') {
 const shouldReconnect = lastDisconnect?.error?.output?.statusCode !== DisconnectReason.loggedOut;
 if (shouldReconnect) connectToWhatsApp();
 } else if (connection === 'open') {
 console.log('Connected! Bot LID:', sock.user?.lid);
 }
 });

 sock.ev.on('messages.upsert', async ({ messages }) => {
 for (const m of messages) {
 if (!m.message) continue;
 console.log('Message:', JSON.stringify(m, undefined, 2));
 }
 });

 sock.ev.on('creds.update', saveCreds);
}

connectToWhatsApp().catch(console.error);
Pairing Code (no QR)
import makeWASocket, { useMultiFileAuthState } from 'toxic-baileys';

async function connectWithPairing() {
 const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys');
 const sock = makeWASocket({ auth: state, printQRInTerminal: false });
 sock.ev.on('creds.update', saveCreds);

 if (!sock.authState.creds.registered) {
 const phoneNumber = '254712345678'; // no + or spaces
 const code = await sock.requestPairingCode(phoneNumber);
 console.log('Pairing Code:', code);
 }
}

connectWithPairing().catch(console.error);

Connection & Configuration

Full Socket Configuration
import makeWASocket, { Browsers, makeCacheableSignalKeyStore } from 'toxic-baileys';
import NodeCache from '@cacheable/node-cache';

const groupCache = new NodeCache({ stdTTL: 300, useClones: false });

const sock = makeWASocket({
 browser: Browsers.macOS('Chrome'),
 syncFullHistory: true,
 markOnlineOnConnect: false,
 connectTimeoutMs: 60_000,
 defaultQueryTimeoutMs: 60_000,
 keepAliveIntervalMs: 30_000,
 generateHighQualityLinkPreview: true,
 cachedGroupMetadata: async (jid) => groupCache.get(jid),
 getMessage: async (key) => await yourStore.getMessage(key),
});

sock.ev.on('groups.update', async ([event]) => {
 const metadata = await sock.groupMetadata(event.id);
 groupCache.set(event.id, metadata);
});

Authentication State Management

Multi-File Auth (Development)
import makeWASocket, { useMultiFileAuthState } from 'toxic-baileys';

const { state, saveCreds } = await useMultiFileAuthState('./auth_info');
const sock = makeWASocket({ auth: state });
sock.ev.on('creds.update', saveCreds);
Custom Database Auth (Production)
import makeWASocket, { makeCacheableSignalKeyStore } from 'toxic-baileys';

const myAuthState = {
 creds: await db.getAuthCreds(),
 keys: makeCacheableSignalKeyStore(await db.getSignalKeys(), console)
};
const sock = makeWASocket({ auth: myAuthState });
sock.ev.on('creds.update', async (creds) => await db.saveAuthCreds(creds));

LID Mapping System

Important

This is one of the most critical new features in this fork. WhatsApp is migrating groups to use Linked Identity (LID) JIDs — identifiers in the @lid domain that replace @s.whatsapp.net phone-number JIDs for privacy and cross-platform reasons. If you receive a message from 1234567890:0@lid and don't have a mapping, you cannot identify the sender.

What is a LID?

A Linked Identity (LID) is an opaque numeric identifier assigned to each WhatsApp account in the @lid domain. WhatsApp uses LIDs in groups to decouple a person's phone number from their group identity. The LID is stable across phone number changes and is used for the Signal encryption protocol in newer group types.

Standard JID: 254712345678@s.whatsapp.net phone number visible
LID JID: 9876543210:0@lid opaque — phone number hidden

How the Mapping Store Works

This fork implements LIDMappingStore in src/Signal/lid-mapping.ts:

┌─────────────────────────────────────────────────────┐
│ LIDMappingStore │
│ │
│ LRU Cache (3-day TTL, auto-purge) │
│ pn:{pnUser} lidUser │
│ lid:{lidUser} pnUser │
│ │
│ SignalKeyStoreWithTransaction │
│ lid-mapping/{pnUser} lidUser │
│ lid-mapping/{lidUser}_reverse pnUser │
│ │
│ USync Lookup (UsyncLIDProtocol) │
│ Query WhatsApp servers via w:sync IQ │
└─────────────────────────────────────────────────────┘

Persistent file naming (with useMultiFileAuthState):

Session/
 lid-mapping-{pnUser}.json pnUser lidUser
 lid-mapping-{lidUser}_reverse.json lidUser pnUser (reverse lookup)

me.lid — Your Bot's Own LID

When your bot pairs with WhatsApp, its own LID is stored in the session credentials:

// Stored automatically by configureSuccessfulPairing()
// Access it any time after connecting:
const myLid = sock.user?.lid;
console.log('My LID:', myLid); // e.g. "9876543210@lid"

lidDbMigrated Login Flag

The login payload includes lidDbMigrated: false. This signals to WhatsApp that the client has not yet migrated its local LID database, which causes WhatsApp to push down a full set of LIDPN mappings on the initial connection — seeding your local store automatically.

API Reference

// On the socket (via SignalRepositoryWithLIDStore):
sock.signalRepository.lidMapping.getPNForLID(lidJid) // string | null
sock.signalRepository.lidMapping.getLIDForPN(pnJid) // string | null
sock.signalRepository.lidMapping.getPNsForLIDs(lidJids[]) // LIDMapping[] | null
sock.signalRepository.lidMapping.getLIDsForPNs(pnJids[]) // LIDMapping[] | null
sock.signalRepository.lidMapping.storeLIDPNMappings(pairs[]) // void

// Type:
type LIDMapping = { lid: string; pn: string }

Live Integration Example

import makeWASocket, { useMultiFileAuthState, makeCacheableSignalKeyStore } from 'toxic-baileys';

const { state, saveCreds } = await useMultiFileAuthState('./Session');
const lidPhoneCache = new Map();

const sock = makeWASocket({
 auth: {
 creds: state.creds,
 keys: makeCacheableSignalKeyStore(state.keys, console)
 }
});

sock.ev.on('creds.update', saveCreds);

// Wire up LID globals for use throughout your bot
globalThis.resolvePhoneFromLid = (lidJid) => {
 // synchronous — only works if the mapping is already cached
 return null;
};

globalThis.resolvePhoneFromLidAsync = async (lidJid) => {
 return await sock.signalRepository.lidMapping.getPNForLID(lidJid);
};

// Keep the in-memory cache warm from live LID mapping events
sock.ev.on('lid-mapping.update', (map) => {
 for (const [lid, pn] of Object.entries(map)) {
 const lidNum = lid.split('@')[0].split(':')[0];
 const phone = String(pn).split('@')[0].split(':')[0].replace(/\D/g, '');
 if (lidNum && phone) lidPhoneCache.set(lidNum, phone);
 }
});

// Resolving a sender from a group message
sock.ev.on('messages.upsert', async ({ messages }) => {
 for (const m of messages) {
 const sender = m.key.participant || m.key.remoteJid || '';
 if (sender.endsWith('@lid')) {
 const resolved = await sock.signalRepository.lidMapping.getPNForLID(sender);
 console.log(`LID ${sender} ${resolved ?? 'unknown'}`);
 }
 }
});

Multi-Layer Resolution (Recommended Pattern)

For maximum reliability, resolve LIDs in this priority order:

async function resolveLid(lidJid, sock, lidPhoneCache) {
 const lidNum = lidJid.split('@')[0].split(':')[0].replace(/\D/g, '');
 if (!lidNum) return null;

 // 1. In-memory LRU cache (fastest)
 const cached = lidPhoneCache?.get(lidNum);
 if (cached) return String(cached).replace(/\D/g, '') + '@s.whatsapp.net';

 // 2. Baileys LIDMappingStore (LRU + persistent key store)
 const fromStore = await sock.signalRepository?.lidMapping?.getPNForLID(lidJid);
 if (fromStore) {
 const num = fromStore.split('@')[0].replace(/\D/g, '');
 if (num) { lidPhoneCache?.set(lidNum, num); return num + '@s.whatsapp.net'; }
 }

 // 3. Session file (lid-mapping-{lidNum}_reverse.json)
 try {
 const revFile = `./Session/lid-mapping-${lidNum}_reverse.json`;
 if (fs.existsSync(revFile)) {
 const jid = JSON.parse(fs.readFileSync(revFile, 'utf-8'));
 const num = String(jid).split('@')[0].replace(/\D/g, '');
 if (num && num !== lidNum) {
 lidPhoneCache?.set(lidNum, num);
 return num + '@s.whatsapp.net';
 }
 }
 } catch {}

 // 4. Group metadata participant scan (network call — use sparingly)
 try {
 const meta = await sock.groupMetadata(chatJid);
 for (const p of meta.participants || []) {
 const pLid = (p.lid || p.id || '').split('@')[0].split(':')[0].replace(/\D/g, '');
 if (pLid !== lidNum) continue;
 const pBase = p.id || p.jid || '';
 if (pBase && !pBase.endsWith('@lid')) {
 const num = pBase.split('@')[0].replace(/\D/g, '');
 if (num) { lidPhoneCache?.set(lidNum, num); return num + '@s.whatsapp.net'; }
 }
 }
 } catch {}

 return null; // unresolvable — pass lidJid through and let WhatsApp handle it
}

Note

Session file cleanup warning: If your bot cleans up session files periodically, make sure lid-mapping-* files are preserved in your keep-list, or your mapping cache will be lost on restart and need to be re-fetched from WhatsApp's servers.


AI Groups

WhatsApp introduced AI-powered groups — special group types that include an AI bot participant. This fork exposes full management of these groups via makeAIGroupsSocket (which is included in the socket chain automatically).

Socket Chain

makeWASocket
 └─ makeInteropSocket
 └─ makeAIGroupsSocket
 └─ makeCommunitiesSocket
 └─ makeGroupsSocket
 └─ ...

AI Group Events

// Automatically fires when an AI group is created
sock.ev.on('groups.upsert', ([groupMeta]) => {
 console.log('New group:', groupMeta.id, groupMeta.subject);
});

// Fires on participant changes (add/remove/promote/demote) in AI groups
sock.ev.on('group-participants.update', ({ id, participants, action }) => {
 console.log(`${action} in ${id}:`, participants);
});

AI Group Metadata

// Fetch full metadata for an AI group (uses interactive query)
const meta = await sock.aiGroupMetadata('1234567890@g.us');
console.log('AI group subject:', meta.subject);
console.log('Participants:', meta.participants);

Add Bot to AI Group

// Add an AI bot participant to an AI group
await sock.aiGroupAddBot('1234567890@g.us');

Interoperability API

WhatsApp's interoperability (interop) system allows messages to be exchanged with third-party messaging platforms. This fork exposes the full interop socket layer via makeInteropSocket.

Supported Integrators

ID Name Notes
12 BirdyChat Cross-platform messaging bridge
13 Haiket Cross-platform messaging bridge

Fetch Available Integrators

const integrators = await sock.fetchIntegrators();
/*
[
 {
 id: 12,
 name: 'BirdyChat',
 status: 'active', // 'active' | 'onboarding' | 'removed'
 identifierType: 'pn', // 'email' | 'pn' | 'username'
 optedIn: false,
 features: { groupMessaging: true }
 },
 ...
]
*/

Accept Interop Terms of Service

// Must be called before opting in to any integrator
await sock.acceptInteropTOS();

Opt In to Integrators

// Opt in to all default integrators (BirdyChat + Haiket)
await sock.optInIntegrators();

// Opt in to specific integrators by ID
await sock.optInIntegrators([12]);

USync Protocol

WAUSync is WhatsApp's batch user-info query system. This fork exports the full USync module so you can run structured queries against WhatsApp's w:sync:user IQ namespace.

Available Protocols

Protocol Class Purpose
USyncContactProtocol Resolve contact info
USyncDeviceProtocol Fetch registered devices
USyncDisappearingModeProtocol Get disappearing message settings
USyncStatusProtocol Fetch user status
USyncTextStatusProtocol Fetch a user's text status (with emoji and expiry)
USyncPictureProtocol Fetch a user's profile picture ID/direct path
USyncUsernameProtocol Resolve usernames
UsyncBotProfileProtocol Fetch AI bot profile data
UsyncLIDProtocol Resolve LID phone-number pairs

Running a USync Query

import { USyncQuery, USyncUser } from 'toxic-baileys';

const query = new USyncQuery()
 .withContext('interactive')
 .withLIDProtocol()
 .withDeviceProtocol();

const user = new USyncUser().withPhone('254712345678@s.whatsapp.net');
query.withUser(user);

const result = await sock.executeUSyncQuery(query);
console.log(result);

MEX / GraphQL Queries

For structured server queries, this fork exposes executeWMexQuery — a GraphQL-style query executor over WhatsApp's w:mex IQ namespace:

import { executeWMexQuery } from 'toxic-baileys';

const result = await executeWMexQuery(
 { userId: '254712345678' }, // variables
 'xwa2_user_profile', // queryId (WhatsApp's internal query name)
 'xwa2_user_profile', // dataPath (key in the response data object)
 sock.query.bind(sock), // query function
 sock.generateMessageTag.bind(sock)
);
console.log('MEX result:', result);

Sending Messages

Basic Messages

Text, Reply, Mention, Forward, Edit, Delete, React
await sock.sendMessage(jid, { text: 'Hello World!' });
await sock.sendMessage(jid, { text: 'Reply!' }, { quoted: m });
await sock.sendMessage(jid, { text: 'Hi @user!', mentions: ['254712345678@s.whatsapp.net'] });
await sock.sendMessage(jid, { forward: m });

const sent = await sock.sendMessage(jid, { text: 'Original' });
await sock.sendMessage(jid, { text: 'Edited!', edit: sent.key });
await sock.sendMessage(jid, { delete: sent.key });

await sock.sendMessage(jid, { react: { text: '', key: m.key } });
await sock.sendMessage(jid, { react: { text: '', key: m.key } }); // remove reaction

Media Messages

Image, Video, Audio, Document, Sticker, View Once
await sock.sendMessage(jid, { image: { url: './image.jpg' }, caption: 'Caption' });
await sock.sendMessage(jid, { image: fs.readFileSync('./img.jpg') });
await sock.sendMessage(jid, { video: { url: './video.mp4' }, caption: 'Video' });
await sock.sendMessage(jid, { video: { url: './animation.mp4' }, gifPlayback: true });
await sock.sendMessage(jid, { audio: { url: './voice.ogg' }, mimetype: 'audio/ogg; codecs=opus', ptt: true });
await sock.sendMessage(jid, { audio: { url: './song.mp3' }, mimetype: 'audio/mp4' });
await sock.sendMessage(jid, { document: { url: './file.pdf' }, fileName: 'MyDoc.pdf', mimetype: 'application/pdf' });
await sock.sendMessage(jid, { sticker: { url: './sticker.webp' } });
await sock.sendMessage(jid, { image: fs.readFileSync('./img.jpg'), viewOnce: true });

Interactive Messages

Interactive Message with Native Flow Buttons
// Copy button
await sock.sendMessage(jid, {
 interactiveMessage: {
 header: 'toxic-baileys™',
 title: 'Hello World',
 footer: 'By 𝐱𝐡_𝐜𝐥𝐢𝐧𝐭𝐨𝐧 ',
 buttons: [
 {
 name: 'cta_copy',
 buttonParamsJson: JSON.stringify({ display_text: 'Copy Code', id: '1', copy_code: 'TOXIC123' })
 }
 ]
 }
}, { quoted: m });

// URL button
await sock.sendMessage(jid, {
 interactiveMessage: {
 header: 'Visit Us',
 title: 'toxic-baileys',
 footer: 'GitHub',
 buttons: [
 {
 name: 'cta_url',
 buttonParamsJson: JSON.stringify({ display_text: 'Open GitHub', url: 'https://github.com/xhclintohn/Baileys' })
 }
 ]
 }
}, { quoted: m });

// With image thumbnail
await sock.sendMessage(jid, {
 interactiveMessage: {
 header: 'With Image',
 title: 'toxic-baileys™',
 footer: 'Best Baileys Fork',
 image: { url: 'https://example.com/image.jpg' },
 buttons: [
 { name: 'cta_copy', buttonParamsJson: JSON.stringify({ display_text: 'Copy', id: '1', copy_code: 'HELLO' }) }
 ]
 }
}, { quoted: m });

// Single select list
await sock.sendMessage(jid, {
 interactiveMessage: {
 header: 'Choose',
 title: 'Menu',
 footer: 'Select below',
 nativeFlowMessage: {
 buttons: [
 {
 name: 'single_select',
 buttonParamsJson: JSON.stringify({
 title: 'Options',
 sections: [
 {
 title: 'Commands',
 rows: [
 { title: 'Option 1', description: 'First', id: 'opt_1' },
 { title: 'Option 2', description: 'Second', id: 'opt_2' }
 ]
 }
 ]
 })
 }
 ]
 }
 }
}, { quoted: m });

// With externalAdReply
await sock.sendMessage(jid, {
 interactiveMessage: {
 header: 'Premium',
 title: 'toxic-baileys™',
 footer: 'By 𝐱𝐡_𝐜𝐥𝐢𝐧𝐭𝐨𝐧',
 externalAdReply: {
 title: 'toxic-baileys',
 body: 'Best WhatsApp Library',
 mediaType: 1,
 thumbnailUrl: 'https://example.com/thumb.jpg',
 sourceUrl: 'https://github.com/xhclintohn/Baileys',
 showAdAttribution: true,
 renderLargerThumbnail: true
 },
 buttons: [
 { name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: 'Visit', url: 'https://github.com/xhclintohn/Baileys' }) }
 ]
 }
}, { quoted: m });
sendInteractive — one call, handles iOS automatically

Interactive buttons don't render reliably when the bot account itself is paired from an iPhone. sendInteractive checks the paired device's platform for you and falls back to a plain text message on iOS instead of sending a button payload that won't show up right:

await sock.sendInteractive(jid, 'Tap below to continue');

// with options
await sock.sendInteractive(jid, 'Tap below to continue', {
 title: 'Bot',
 subtitle: 'Q',
 footer: 'In-app signup',
 buttonName: 'inapp_signup',
 quoted: m
});

// same function, other names if you prefer them
await sock.inappsignup(jid, 'Tap below to continue');
await sock.inapp_signup(jid, 'Tap below to continue');

Album Messages

Send multiple images/videos in a single album
await sock.sendMessage(jid, {
 albumMessage: [
 { image: fs.readFileSync('./photo1.jpg'), caption: 'First photo' },
 { image: { url: 'https://example.com/photo2.jpg' }, caption: 'Second photo' },
 { video: fs.readFileSync('./clip.mp4'), caption: 'A video clip' }
 ]
}, { quoted: m });

Event Messages

Send WhatsApp event invitations
await sock.sendMessage(jid, {
 eventMessage: {
 isCanceled: false,
 name: 'toxic-baileys Launch',
 description: 'Join us for the launch!',
 location: { degreesLatitude: -1.2921, degreesLongitude: 36.8219, name: 'Nairobi, Kenya' },
 joinLink: 'https://call.whatsapp.com/video/your-link',
 startTime: String(Math.floor(Date.now() / 1000) + 3600),
 endTime: String(Math.floor(Date.now() / 1000) + 7200),
 extraGuestsAllowed: true
 }
}, { quoted: m });

Poll & Poll Result Messages

Polls and displaying poll results
// Create poll
await sock.sendMessage(jid, {
 poll: {
 name: 'Best WhatsApp library?',
 values: ['toxic-baileys', 'Baileys', 'Other'],
 selectableCount: 1
 }
});

// Display poll results
await sock.sendMessage(jid, {
 pollResultMessage: {
 name: 'Best Library Results',
 pollVotes: [
 { optionName: 'toxic-baileys', optionVoteCount: '42' },
 { optionName: 'Baileys', optionVoteCount: '10' },
 { optionName: 'Other', optionVoteCount: '2' }
 ]
 }
}, { quoted: m });

Group Status Messages

Post status/story to a group (Group Status V2)

There are two ways to post a group status. toxicHandler gives you a groupStatusMessage wrapper that takes care of the details for you:

await sock.sendMessage(groupJid, { groupStatusMessage: { text: 'Hello group! ' } });
await sock.sendMessage(groupJid, { groupStatusMessage: { image: fs.readFileSync('./banner.jpg'), caption: 'Update!' } });
await sock.sendMessage(groupJid, { groupStatusMessage: { video: fs.readFileSync('./promo.mp4'), caption: 'Promo' } });
await sock.sendMessage(groupJid, { groupStatusMessage: { audio: fs.readFileSync('./audio.mp4'), mimetype: 'audio/mp4' } });

// Using ToxicHandler directly (exposed on socket)
const storyResult = await sock.toxicHandler.handleGroupStory(content, groupJid, quotedMsg);

Or send a regular media/text message with the status contextInfo set directly — this is what you'll want if you're building a command like .gstatus that reposts whatever a user replies with:

const buffer = await downloadMediaMessage(quotedImageMessage, 'buffer');

await sock.sendMessage(groupJid, {
 image: buffer,
 caption: 'Update!',
 contextInfo: {
 isGroupStatus: true,
 statusSourceType: 'IMAGE', // IMAGE | VIDEO | GIF | AUDIO | TEXT | MUSIC_STANDALONE
 statusAttributions: [{ type: 10 }],
 statusAudienceMetadata: { audienceType: 'CLOSE_FRIENDS' }
 }
});

// Same idea for video, audio, or plain text — just swap the content key
await sock.sendMessage(groupJid, {
 text: 'Check out this update!',
 contextInfo: {
 isGroupStatus: true,
 statusSourceType: 'TEXT',
 statusAttributions: [{ type: 10 }],
 statusAudienceMetadata: { audienceType: 'CLOSE_FRIENDS' }
 }
});

Payment Request Messages

Send payment requests
await sock.sendMessage(jid, {
 requestPaymentMessage: {
 currency: 'KES',
 amount: 500000,
 from: m.sender,
 background: { id: 'DEFAULT', placeholderArgb: 0xFFF0F0F0 }
 }
}, { quoted: m });

Product Messages

Send product catalog messages
await sock.sendMessage(jid, {
 productMessage: {
 title: 'Premium Script',
 description: 'Best bot script available',
 thumbnail: { url: 'https://example.com/product.jpg' },
 productId: 'PROD001',
 retailerId: 'xhclinton',
 url: 'https://github.com/xhclintohn/Baileys',
 body: 'Full featured automation',
 footer: 'Special price',
 priceAmount1000: 10000,
 currencyCode: 'USD',
 buttons: [{ name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: 'Buy Now', url: 'https://github.com/xhclintohn/Baileys' }) }]
 }
}, { quoted: m });

Button & Template Messages

Classic buttons, list messages, template buttons
// Classic buttons
await sock.sendMessage(jid, {
 text: 'Choose:',
 footer: 'toxic-baileys™',
 buttons: [
 { buttonId: 'btn_1', buttonText: { displayText: 'Option 1' }, type: 1 },
 { buttonId: 'btn_2', buttonText: { displayText: 'Option 2' }, type: 1 }
 ],
 headerType: 1
});

// List message
await sock.sendMessage(jid, {
 text: 'Pick one:',
 footer: 'toxic-baileys™',
 title: 'Menu',
 buttonText: 'Open List',
 sections: [
 {
 title: 'Section 1',
 rows: [
 { title: 'Item 1', rowId: 'row_1', description: 'Description 1' },
 { title: 'Item 2', rowId: 'row_2', description: 'Description 2' }
 ]
 }
 ]
});

Rich Message Builders

The WABuilder module ships as part of the package now — Button, ButtonV2, Carousel, AIRich, and Toolkit. Each builder takes your socket instance and gives you a chainable API instead of hand-writing raw message payloads.

Every class is also exported under a few shorter names, in case you'd rather import it as something else:

import { AIRich, RichMessage, Rich, RichMsg, RichAI } from 'toxic-baileys'; // all the same class
import { Button, Buttons, Btns } from 'toxic-baileys'; // all the same class
import { ButtonV2, ButtonsV2, BtnsV2, NewButtons } from 'toxic-baileys'; // all the same class
Button — media + button rows
import { Button } from 'toxic-baileys';

const btn = new Button(sock)
 .setTitle('Choose an option')
 .setBody('Pick one of the buttons below')
 .setFooter('Powered by toxic-baileys')
 .setImage('./banner.jpg')
 .addReply('Option A', 'opt_a')
 .addReply('Option B', 'opt_b')
 .addUrl('Visit site', 'https://example.com');

await btn.send(jid);
ButtonV2 — native interactive buttons
import { ButtonV2 } from 'toxic-baileys';

const btn = new ButtonV2(sock)
 .setBody('Native button layout')
 .addButton('Click me', 'click_id');

await btn.send(jid);
Carousel — swipeable cards
import { Carousel } from 'toxic-baileys';

const carousel = new Carousel(sock);
// build cards via the card API, then:
await carousel.send(jid);
AIRich — Meta AI-style rich responses

Builds the same rich response format Meta AI uses in WhatsApp: text with inline citations/hyperlinks/LaTeX, code blocks, tables, and source lists, all rendered as one message.

import { AIRich } from 'toxic-baileys';

const reply = new AIRich(sock)
 .setTitle('Answer')
 .addText('Here is your answer, with a [source](https://example.com) cited inline.')
 .addCode('javascript', 'console.log("hello world")')
 .addTable([
 ['Name', 'Value'],
 ['foo', '123']
 ]);

await reply.send(jid, { quoted: m });

Toolkit is the set of static helpers AIRich and the other builders use internally (Toolkit.resize, Toolkit.fetchBuffer, Toolkit.resolveMedia, Toolkit.getMp4Preview, etc.) — useful on their own if you're building custom payloads.

Image resizing goes through sharp and video thumbnail extraction through fluent-ffmpeg, both optional — if they're not installed, those specific features fall back gracefully instead of throwing.


Chat & Message Management

Read receipts, archive, pin, delete, star
await sock.readMessages([m.key]);
await sock.sendReadReceipt(jid, participant, [m.key.id]);
await sock.chatModify({ archive: true, lastMessages: [{ key: m.key, messageTimestamp: m.messageTimestamp }] }, jid);
await sock.chatModify({ pin: true }, jid);
await sock.chatModify({ delete: true, lastMessages: [{ key: m.key, messageTimestamp: m.messageTimestamp }] }, jid);
await sock.chatModify({ star: { messages: [{ id: m.key.id, fromMe: m.key.fromMe }], star: true } }, jid);
await sock.sendMessage(jid, { delete: m.key });

Group Management

Create, modify, manage participants
const group = await sock.groupCreate('Group Name', ['254712345678@s.whatsapp.net']);
await sock.groupParticipantsUpdate(jid, ['254712345678@s.whatsapp.net'], 'add');
await sock.groupParticipantsUpdate(jid, ['254712345678@s.whatsapp.net'], 'remove');
await sock.groupParticipantsUpdate(jid, ['254712345678@s.whatsapp.net'], 'promote');
await sock.groupParticipantsUpdate(jid, ['254712345678@s.whatsapp.net'], 'demote');
await sock.groupUpdateSubject(jid, 'New Name');
await sock.groupUpdateDescription(jid, 'New description');
await sock.groupSettingUpdate(jid, 'announcement');
await sock.groupSettingUpdate(jid, 'not_announcement');
await sock.groupSettingUpdate(jid, 'locked');
await sock.groupSettingUpdate(jid, 'unlocked');
await sock.groupLeave(jid);
const inviteCode = await sock.groupInviteCode(jid);
const meta = await sock.groupMetadata(jid);
const all = await sock.groupFetchAllParticipating();

User & Profile Management

Profile photo, status, presence
await sock.updateProfilePicture(jid, { url: './avatar.jpg' });
await sock.removeProfilePicture(jid);
const pp = await sock.profilePictureUrl(jid, 'image');
await sock.updateProfileStatus('Hey there! I am using toxic-baileys™');
await sock.updateProfileName('My Bot Name');
await sock.fetchStatus(jid);
const exists = await sock.onWhatsApp('254712345678@s.whatsapp.net');
await sock.sendPresenceUpdate('available', jid);
await sock.sendPresenceUpdate('composing', jid);
await sock.sendPresenceUpdate('recording', jid);
await sock.sendPresenceUpdate('paused', jid);

Username Management

WhatsApp usernames (the @username handles, separate from phone numbers) — checking availability, claiming one, and looking people up by theirs.

Set and manage your own username
// Check if a username is free before you try to claim it
const check = await sock.checkUsername('my_bot_name');
if (check.available) {
 await sock.setUsername('my_bot_name');
} else {
 console.log('Taken. Suggestions:', check.suggestions);
}

// Read it back
const current = await sock.getMyUsername();

// Protect it with a PIN
await sock.setUsernamePin('1234');
await sock.setUsernamePin(null); // remove the PIN

// Give it up
await sock.deleteUsername();

// Get suggestions without checking a specific name first
const recs = await sock.getUsernameRecommendations();
Look people up by username
// Resolve a username to a JID
const user = await sock.findUserByUsername('someone_cool');
if (user) {
 console.log(user.jid);
}

// Batch-fetch usernames for JIDs you already have
const results = await sock.fetchContactUsernames(jid1, jid2, jid3);

// Check several usernames for availability at once
const results2 = await sock.checkUsernameMulti(['name_one', 'name_two']);
Fetch a username or text status via USync directly
import { USyncQuery, USyncUser } from 'toxic-baileys';

const query = new USyncQuery()
 .withUsernameProtocol()
 .withTextStatusProtocol()
 .withUser(new USyncUser().withId(jid));

const result = await sock.executeUSyncQuery(query);
console.log(result?.list[0]);

Newsletter / Channel Management

Create, follow, manage newsletters and channels
import { newsletterId } from 'toxic-baileys';

// Follow a channel
await sock.newsletterFollow('1234567890@newsletter');

// Unfollow
await sock.newsletterUnfollow('1234567890@newsletter');

// Fetch channel metadata
const meta = await sock.newsletterMetadata('invite', 'your-invite-link');
console.log(meta.id, meta.name, meta.subscribers);

// Create a channel
const channel = await sock.newsletterCreate('Channel Name', { description: 'My channel' });

// Send a message to your channel
await sock.sendMessage('1234567890@newsletter', { text: 'Channel update!' });

// newsletterId() helper — extract clean ID from any format
const cleanId = newsletterId('1234567890@newsletter');
const cleanIdFromLink = newsletterId('https://whatsapp.com/channel/yourlink');
console.log('Clean ID:', cleanId);

// Mute/unmute
await sock.newsletterMute('1234567890@newsletter');
await sock.newsletterUnmute('1234567890@newsletter');

Privacy & Block Management

Privacy settings and block list
await sock.updateProfilePicturePrivacy('contacts'); // 'all' | 'contacts' | 'contact_blacklist' | 'none'
await sock.updateStatusPrivacy('contacts');
await sock.updateReadReceiptsPrivacy('all'); // 'all' | 'none'
await sock.updateGroupsAddPrivacy('contacts');
await sock.updateLastSeenPrivacy('contacts');
await sock.updateOnlinePrivacy('all');

const privacy = await sock.fetchPrivacySettings(true);

await sock.updateBlockStatus(jid, 'block');
await sock.updateBlockStatus(jid, 'unblock');
const blocked = await sock.fetchBlocklist();

Data Store Implementation

In-Memory Store (Development)
import makeWASocket, { makeInMemoryStore } from 'toxic-baileys';

const store = makeInMemoryStore({ logger: console });
store.readFromFile('./baileys_store.json');
setInterval(() => store.writeToFile('./baileys_store.json'), 10_000);

const sock = makeWASocket({});
store.bind(sock.ev);
Using ToxicHandler Directly
// ToxicHandler and toxicHandler are both exposed on the socket
const { toxicHandler } = sock;

const paymentContent = await toxicHandler.handlePayment(content, quoted);
const interactive = await toxicHandler.handleInteractive(content, jid, quoted);
const album = await toxicHandler.handleAlbum(content, jid, quoted);
const event = await toxicHandler.handleEvent(content, jid, quoted);
const pollResult = await toxicHandler.handlePollResult(content, jid, quoted);
const groupStory = await toxicHandler.handleGroupStory(content, jid, quoted);

Utility Functions

Core Utilities
import {
 getContentType, areJidsSameUser, isJidGroup, isJidBroadcast,
 isJidStatusBroadcast, isJidNewsLetter, jidNormalizedUser,
 isLidUser, isPnUser, isHostedPnUser,
 generateMessageID, generateMessageIDV2, generateWAMessage,
 generateWAMessageContent, generateWAMessageFromContent,
 downloadContentFromMessage, getAggregateVotesInPollMessage,
 extractMessageContent, normalizeMessageContent, newsletterId,
 proto
} from 'toxic-baileys';

const type = getContentType(m.message);
console.log(isJidGroup('123@g.us')); // true
console.log(isJidNewsLetter('123@newsletter')); // true
console.log(isLidUser('12345@lid')); // true

// Extract clean newsletter ID
const id = newsletterId('https://whatsapp.com/channel/mylink');

// Download media
const stream = await downloadContentFromMessage(m.message.imageMessage, 'image');
const chunks = [];
for await (const chunk of stream) chunks.push(chunk);
const buffer = Buffer.concat(chunks);

// Override the media host if you're routing through your own proxy/CDN
const proxied = await downloadContentFromMessage(m.message.imageMessage, 'image', {
 host: 'my-media-proxy.example.com'
});

// Aggregate poll votes
const votes = getAggregateVotesInPollMessage(
 { message: pollMsg.message, pollUpdates },
 sock.user.id
);

Best Practices & Tips

Connection Stability

  1. Always implement reconnect logic on connection === 'close'
  2. Cache group metadata using cachedGroupMetadata
  3. Use markOnlineOnConnect: false to still receive phone notifications
  4. Set syncFullHistory: true for complete history
  5. If you're spinning up a fresh socket after a logout instead of reconnecting, drop the old sock.ev reference — it clears its own timers and listeners once the socket ends, so you don't need to null it out manually

LID Mapping

  1. Always preserve lid-mapping-* files in session cleanup routines
  2. Listen to the lid-mapping.update event to keep your in-memory cache warm
  3. Fall back to group metadata scan only when all cache/store lookups fail
  4. Use sock.user?.lid to access your own bot's LID identity

Performance

  1. Use @cacheable/node-cache for group metadata caching
  2. Implement a message queue with rate limiting for bulk sends
  3. Use databases instead of in-memory store for production
Auto-reconnect pattern
async function connectWithRetry(maxRetries = 10) {
 let attempt = 0;
 const connect = async () => {
 attempt++;
 try {
 const { state, saveCreds } = await useMultiFileAuthState('./auth');
 const sock = makeWASocket({ auth: state });
 sock.ev.on('creds.update', saveCreds);
 sock.ev.on('connection.update', async ({ connection, lastDisconnect }) => {
 if (connection === 'close') {
 const code = lastDisconnect?.error?.output?.statusCode;
 if (code === DisconnectReason.loggedOut) return;
 const delay = Math.min(5000 * attempt, 60000);
 if (attempt < maxRetries) setTimeout(connect, delay);
 } else if (connection === 'open') {
 attempt = 0;
 }
 });
 } catch (err) {
 if (attempt < maxRetries) setTimeout(connect, 5000 * attempt);
 }
 };
 await connect();
}
Message queue with rate limiting
class MessageQueue {
 constructor(sock, delayMs = 1000) {
 this.sock = sock;
 this.queue = [];
 this.processing = false;
 this.delayMs = delayMs;
 }
 async add(jid, content, options = {}) {
 this.queue.push({ jid, content, options });
 if (!this.processing) this._process();
 }
 async _process() {
 this.processing = true;
 while (this.queue.length > 0) {
 const { jid, content, options } = this.queue.shift();
 try {
 await this.sock.sendMessage(jid, content, options);
 await new Promise(r => setTimeout(r, this.delayMs));
 } catch (err) {
 console.error('Send failed:', err.message);
 }
 }
 this.processing = false;
 }
}

const queue = new MessageQueue(sock, 1500);
await queue.add(jid, { text: 'Message 1' });
await queue.add(jid, { text: 'Message 2' });

Important Legal Notice

This project is NOT affiliated with, authorized, maintained, sponsored, or endorsed by WhatsApp LLC or any of its affiliates.

  • Only message users who have explicitly consented
  • Do NOT use for spamming, bulk unsolicited messaging, or harassment
  • Respect WhatsApp's Terms of Service and rate limits
  • The maintainer assumes NO liability for misuse or damages

Getting Help

  1. GitHub Issuesgithub.com/xhclintohn/Baileys/issues
  2. Response Time — Typically within 24–48 hours

WhatsApp Chat GitHub Follow


License

MIT License. See LICENSE for details.

Credits: Original Baileys by WhiskeySockets · toxic-baileys enhancements by **𝐱𝐡_𝐜𝐥𝐢𝐧𝐭𝐨𝐧 **


toxic-baileys™ — Crafted with by **𝐱𝐡_𝐜𝐥𝐢𝐧𝐭𝐨𝐧 **

The most powerful WhatsApp automation toolkit

Star the repository if this helped you!

About

xD

Resources

License

Stars

16 stars

Watchers

2 watching

Forks

Packages

 
 
 

Contributors