toxic-baileys
v1.2.4
Published
WhatsApp API Enhancement — Feature-rich fork of Baileys by xhclintohn
Maintainers
Readme
If you find this project helpful, consider following me on GitHub.
toxic-baileys™
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
@liddomain JIDs instead of standard@s.whatsapp.netphone-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) andUsyncLIDProtocolso your bot always knows the real phone number behind every@lidJID. See the LID Mapping System section for full integration details.
Table of Contents
- What's New
- Features
- Installation
- Quick Start
- Connection & Configuration
- Authentication State Management
- LID Mapping System
- AI Groups
- Interoperability API
- USync Protocol
- Sending Messages
- Rich Message Builders
- Chat & Message Management
- Group Management
- User & Profile Management
- Username Management
- Newsletter / Channel Management
- Privacy & Block Management
- Data Store Implementation
- Utility Functions
- Best Practices & Tips
- Important Legal Notice
- Getting Help
- License
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 addedUSyncPictureProtocolandUSyncTextStatusProtocolfor fetching profile pictures and text statuses via USync directly. - New
AIRich,Button,ButtonV2,Carousel, andToolkitbuilders. 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(akainappsignup/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.
generateProfilePicturewas 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. getChatIdfails loudly instead of quietly. MissingremoteJid/participantnow throws a clear error instead of silently working withundefined.- Profile picture fetching cleaned up to match the newer, simpler query-building approach upstream uses.
- Group presence updates now carry
groupOnlineCountwhen 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 Friendly —
toxicHandlerandToxicHandlerexposed on socket,fetchAccountReachoutTimelock,fetchNewChatMessageCap,registerSocketEndHandler, clean API - WebSocket Improvements —
perMessageDeflate: false, 100 MB max payload
Installation
Via npm
npm install toxic-baileysVia Yarn
yarn add toxic-baileysFrom GitHub (edge)
npm install github:xhclintohn/BaileysDrop-in replacement for @whiskeysockets/baileys
{
"dependencies": {
"@whiskeysockets/baileys": "npm:toxic-baileys@latest"
}
}Quick Start
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);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
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
import makeWASocket, { useMultiFileAuthState } from 'toxic-baileys';
const { state, saveCreds } = await useMultiFileAuthState('./auth_info');
const sock = makeWASocket({ auth: state });
sock.ev.on('creds.update', saveCreds);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
@liddomain that replace@s.whatsapp.netphone-number JIDs for privacy and cross-platform reasons. If you receive a message from1234567890:0@lidand 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: [email protected] phone number visible
LID JID: 9876543210:0@lid opaque — phone number hiddenHow 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('[email protected]');
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('[email protected]');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('[email protected]');
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
await sock.sendMessage(jid, { text: 'Hello World!' });
await sock.sendMessage(jid, { text: 'Reply!' }, { quoted: m });
await sock.sendMessage(jid, { text: 'Hi @user!', mentions: ['[email protected]'] });
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 reactionMedia Messages
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
// 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 });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
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
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
// 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
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
await sock.sendMessage(jid, {
requestPaymentMessage: {
currency: 'KES',
amount: 500000,
from: m.sender,
background: { id: 'DEFAULT', placeholderArgb: 0xFFF0F0F0 }
}
}, { quoted: m });Product 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
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 classimport { 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);import { ButtonV2 } from 'toxic-baileys';
const btn = new ButtonV2(sock)
.setBody('Native button layout')
.addButton('Click me', 'click_id');
await btn.send(jid);import { Carousel } from 'toxic-baileys';
const carousel = new Carousel(sock);
// build cards via the card API, then:
await carousel.send(jid);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
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
const group = await sock.groupCreate('Group Name', ['[email protected]']);
await sock.groupParticipantsUpdate(jid, ['[email protected]'], 'add');
await sock.groupParticipantsUpdate(jid, ['[email protected]'], 'remove');
await sock.groupParticipantsUpdate(jid, ['[email protected]'], 'promote');
await sock.groupParticipantsUpdate(jid, ['[email protected]'], '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
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('[email protected]');
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.
// 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();// 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']);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
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
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
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);// 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
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('[email protected]')); // 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
- Always implement reconnect logic on
connection === 'close' - Cache group metadata using
cachedGroupMetadata - Use
markOnlineOnConnect: falseto still receive phone notifications - Set
syncFullHistory: truefor complete history - If you're spinning up a fresh socket after a logout instead of reconnecting, drop the old
sock.evreference — it clears its own timers and listeners once the socket ends, so you don't need to null it out manually
LID Mapping
- Always preserve
lid-mapping-*files in session cleanup routines - Listen to the
lid-mapping.updateevent to keep your in-memory cache warm - Fall back to group metadata scan only when all cache/store lookups fail
- Use
sock.user?.lidto access your own bot's LID identity
Performance
- Use
@cacheable/node-cachefor group metadata caching - Implement a message queue with rate limiting for bulk sends
- Use databases instead of in-memory store for production
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();
}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
- GitHub Issues — github.com/xhclintohn/Baileys/issues
- Response Time — Typically within 24–48 hours
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!
