@rennzsync/baileys
v10.7.3
Published
renz/baileys — WhatsApp Multi-Device library rebased @whiskeysockets/baileys 7.0.0-rc14, using the original libsignal (GPL-3.0) Signal Protocol engine. Maintained by RennZSync. Focus: multimedia WhatsApp bots, low RAM footprint.
Maintainers
Readme
rennzsync/baileys
Lightweight WhatsApp Bot library — fully rebased onto @whiskeysockets/baileys 7.0.0-rc14
Baca dalam Bahasa Indonesia → README.id.md
A WhatsApp Multi-Device library rebased onto Baileys v7 rc14, using the original Signal Protocol engine (libsignal, GPL-3.0) — the same one stock @whiskeysockets/baileys rc14 ships with.
License note:
rennzsync/baileys's own code is MIT, but it depends onlibsignal(GPL-3.0) at runtime for the Signal Protocol crypto. If you're publishing or redistributing this package, check what GPL-3.0 compliance means for your use case — this isn't legal advice.
Project focus: multimedia WhatsApp bots — audio, video, image and sticker pipelines, plus Rich WebUI (inline HTML interfaces rendered inside chat bubbles), with RAM-friendly defaults.
Highlights
- Full Baileys 7.0.0-rc14 parity — complete TC-token implementation (trusted contact tokens with expiry & re-issue), Signal Repository API v7 (
getSessionInfo,hasSenderKey,getSenderKeyDistributionMessage), new QR/pairing format, reachout timelock handling. - Standard Signal Protocol engine — uses
libsignaldirectly, same as stock Baileys rc14; no custom native crypto to maintain. - Centralized multimedia pipeline —
media-processorutilities (ffmpeg/sharp/audio-decode, lazy-loaded). - Rich WebUI — render HTML/CSS/JS interfaces directly inside chat bubbles via
sendInlineWebUI. - Widgets & rich menus (10.5.0) —
sock.sendA2UI(A2UI widgets),sock.richMenu, andviewOnceV2/viewOnceV2Extensionsend options. - Original
relayMessage+ shortcut layer (10.6.1) —relayMessageis upstream Baileys again;isSecret/protected/meflags, auto<biz>for all button types and zapo-js-styletype/mediatyperesolution sit in a wrapper on top. - RAM-friendly by default —
syncFullHistory: false,enableRecentMessageCache: false, moderate cache TTLs.
Requirements
| Requirement | Version | |---|---| | Node.js | >= 20.0.0 |
Platform support follows whatever libsignal (^6.0.0) supports on your system — see that package's own docs for prebuilt binary/platform coverage.
Installation
npm install @rennzsync/baileysOptional dependencies (install per feature)
| Package | Feature |
|---|---|
| audio-decode | Voice note waveform (ptt: true) — required for voice notes |
| sharp | Image resize/compression |
| fluent-ffmpeg | Video/audio conversion, video thumbnails |
| jimp (^1.6.1) | Alternative thumbnails (without sharp) |
| link-preview-js | Link previews |
Quick Start
import makeWASocket, { useMultiFileAuthState } from '@rennzsync/baileys';
const { state, saveCreds } = await useMultiFileAuthState('auth_info');
const sock = makeWASocket({
auth: state,
printQRInTerminal: true
});
sock.ev.on('creds.update', saveCreds);
sock.ev.on('messages.upsert', async ({ messages }) => {
const msg = messages[0];
if (!msg.message || msg.key.fromMe) return;
const jid = msg.key.remoteJid;
const text = msg.message.conversation || msg.message.extendedTextMessage?.text || '';
if (text === '!ping') {
await sock.sendMessage(jid, { text: 'pong' }, { quoted: msg });
}
});Examples: Multimedia
Send an image with caption
await sock.sendMessage(jid, {
image: { url: 'https://example.com/photo.jpg' },
caption: 'Hello!'
});Send a voice note (PTT)
// requires: npm install audio-decode
await sock.sendMessage(jid, {
audio: { url: './voice.ogg' },
mimetype: 'audio/ogg; codecs=opus',
ptt: true
});Convert video/audio before sending (media-processor)
import { convertToWhatsAppVideo, convertToOpusAudio, getVideoThumbnail, resizeImage } from '@rennzsync/baileys';
// Any video -> WhatsApp-compatible MP4/H.264 (requires fluent-ffmpeg)
const mp4 = await convertToWhatsAppVideo(rawBuffer);
await sock.sendMessage(jid, { video: mp4, caption: 'Converted video' });
// Any audio -> OGG/Opus for voice notes
const opus = await convertToOpusAudio(audioBuffer);
// Video thumbnail & image resize (requires sharp)
const thumb = await getVideoThumbnail(mp4, 1);
const small = await resizeImage(imageBuffer, { width: 300, height: 300 });Probe media metadata
import { probeMedia, getMp4Duration } from '@rennzsync/baileys';
const meta = await probeMedia(buffer, 'audio/mpeg'); // { duration, bitrate, container, codec }
const dur = getMp4Duration(mp4Buffer); // no ffmpeg needed — parses atoms directlyExamples: Rich WebUI (inline HTML in chat bubbles)
Send an HTML/CSS/JS interface that renders directly inside the message bubble — great for interactive menus, mini-apps and dashboards:
import { sendInlineWebUI } from '@rennzsync/baileys';
const html = `<!DOCTYPE html>
<html><head><style>body{background:#111b21;color:#fff;font-family:sans-serif;padding:16px}</style></head>
<body><h2>Bot Menu</h2><button onclick="alert('hi')">Press me</button></body></html>`;
await sendInlineWebUI(sock, jid, html, 'Bot Menu');
// Identity can be overridden (default: Meta AI)
await sendInlineWebUI(sock, jid, html, 'Bot Menu', {
botJid: '12345@bot',
forwardOrigin: 'CUSTOM'
});Note: the HTML primitive name (
GenAIaeacdsnwHtmlPrimitive) is an obfuscated WhatsApp Web identifier and may change between WA versions. If the WebUI stops rendering, update the identifier from the latest WA Web bundle.
Examples: Classic buttons & lists (render everywhere, new in 10.0.1)
interactiveMessage + nativeFlowMessage cards are no longer rendered on many WhatsApp clients — relayMessage succeeds without error but the message silently doesn't appear. The classic buttonsMessage and listMessage templates render reliably on every client (Android/iOS/Web/Desktop).
@rennzsync/[email protected] ships ready-made builders in lib/Utils/rich-classic.js:
import { buildButtonsMessage, buildListMessage, sendClassicMessage } from '@rennzsync/baileys';
// 1-3 quick-reply buttons (optionally with a location+thumbnail header)
const buttons = buildButtonsMessage({
text: 'Hello Brother — pick an option',
footer: '© My Bot',
buttons: [
{ buttonId: '.owner', buttonText: 'Owner' },
{ buttonId: '.allmenu', buttonText: 'Allmenu' },
],
locationMessage: { jpegThumbnail, name: 'My Bot', address: 'v10.0.1' },
});
// Scrollable list with sections and rows
const list = buildListMessage({
title: 'Menu — 1271 commands',
description: 'Pick a category',
buttonText: 'Pilih Kategori',
sections: [{
title: 'Categories',
rows: [
{ title: 'main', description: '19 commands', rowId: '.menucat main' },
{ title: 'sticker', description: '42 commands', rowId: '.menucat sticker' },
],
}],
});
// Send via relayMessage — userJid is normalized automatically
// (handles sock.user.id vs the legacy non-existent sock.user.jid)
await sendClassicMessage(sock, jid, buttons);
await sendClassicMessage(sock, jid, list);Also exports normalizeUserJid(sockOrUserOrJid) — sock.user.jid never existed in baileys 7.x (sock.user is creds.me, which has .id); this helper accepts any shape and returns a valid jid.
Examples: Carousel & native-flow buttons (new in 10.0.3, risk-aware)
lib/Utils/rich-carousel.js adds interactiveMessage/carouselMessage builders (the horizontal-scroll card format), for cases where you specifically need carousel or richer CTA button types (cta_url, cta_call, cta_copy, cta_reminder, single_select) that the classic templates above don't cover. Same rendering caveat as always: prefer rich-classic.js unless you specifically need these.
import { buildCarouselMessage, sendCarouselMessage, sendInteractiveMessage } from '@rennzsync/baileys';
// single native-flow bubble (no carousel)
await sendInteractiveMessage(sock, jid, {
text: 'Choose an action:',
footer: '© My Bot',
buttons: [
{ type: 'quick_reply', displayText: 'Owner', id: '.owner' },
{ type: 'cta_url', displayText: 'Docs', url: 'https://github.com/RennZSync/baileys' },
],
});
// multi-card carousel
await sendCarouselMessage(sock, jid, {
text: 'Pick a product:',
cards: [
{
header: { title: 'Product A', mediaKind: 'thumbnail', jpegThumbnail },
text: 'Rp 50.000',
buttons: [{ type: 'quick_reply', displayText: 'Buy', id: 'buy_a' }],
},
{
header: { title: 'Product B', mediaKind: 'thumbnail', jpegThumbnail },
text: 'Rp 75.000',
buttons: [{ type: 'quick_reply', displayText: 'Buy', id: 'buy_b' }],
},
],
});Lower-level buildInteractiveCard(...) and buildNativeFlowButton(...) are also exported if you want the raw content object instead of the send-shortcut.
Full-size profile pictures (new in 10.0.3)
generateProfilePicture (used by updateProfilePicture and newsletterUpdatePicture) now accepts { full: true } to skip the forced 640×640 crop/quality-50 downscale and upload closer to the source resolution/quality instead:
await sock.updateProfilePicture(sock.user.id, imageBuffer, { full: true }); // ~quality 92, no resize
await sock.updateProfilePicture(sock.user.id, imageBuffer, { width: 1024, height: 1024, quality: 85 }); // custom
await sock.updateProfilePicture(sock.user.id, imageBuffer); // unchanged default behavior (640x640, q50)In-memory / cache-manager Store & SQLite auth (new in 10.1.0)
lib/Store/* ships a chat/contact/message store you can wire into sock.ev, plus two auth-state backends:
import makeWASocket, { makeInMemoryStore, useSqliteAuthState, useMultiFileAuthState } from '@rennzsync/baileys';
const store = makeInMemoryStore({});
store.readFromFile('./store.json');
setInterval(() => store.writeToFile('./store.json'), 10_000);
// SQLite auth (requires Node >= 22.5, built-in node:sqlite — throws a clear error on older Node)
const { state, saveCreds } = await useSqliteAuthState('./auth.db');
// or the classic file-based one, unchanged:
// const { state, saveCreds } = await useMultiFileAuthState('auth_info');
const sock = makeWASocket({ auth: state });
store.bind(sock.ev);
sock.ev.on('creds.update', saveCreds);makeCacheManagerAuthState is also available if you'd rather back auth state with a cache-manager store (optional peer dep, only required if you actually call it).
sock.sendMessage(jid, content, { isSecret, protected, me }) — three extra device/recipient filters ported from @vansnowi/baileys, forwarded straight through to relayMessage.
Image poll (new in 10.2.0)
Send a poll where each option is an image instead of plain text (pollCreationMessageV3 with pollContentType: IMAGE, one pollCreationOptionImageMessage per option, associated back to the parent poll via MEDIA_POLL):
import { generateWAMessageFromImagePoll } from '@rennzsync/baileys';
await generateWAMessageFromImagePoll(jid, {
name: 'Pilih gambar favorit',
selectableCount: 1,
options: [
{ name: 'Gambar 1', image: { url: 'https://example.com/1.jpg' } },
{ name: 'Gambar 2', image: { url: 'https://example.com/2.jpg' } },
]
}, (msg, opts) => sock.relayMessage(jid, msg, opts), { upload: sock.waUploadToServer });hashImagePollOption(optionName, fileSha256) is also exported separately if you need the option hash without the full send flow. Note: this is a client-side implementation of a message type WhatsApp's own proto defines but no Baileys fork (upstream included) ships a builder for — treat it as experimental and verify rendering on your target WA client build before relying on it in production.
View Once V2 / V2 Extension (new in 10.5.0)
await sock.sendMessage(jid, { text: 'Secret message', viewOnceV2Extension: true }) // viewOnceMessageV2Extension
await sock.sendMessage(jid, { text: 'Secret message', viewOnceV2: true }) // viewOnceMessageV2Wraps the message in viewOnceMessageV2Extension / viewOnceMessageV2; for text, viewOnce: true is set on the inner extendedTextMessage.
Rich Menu (new in 10.5.0, risk-aware)
Port of richMenu from @vansnowi/baileys (also exported as buildRichMenuMessage / sendRichMenu from lib/Utils/rich-menu.js). Buttons are CTA widgets with a toast, not quick-replies that send a message back. Uses the internal GenAI unifiedResponse format, so it may not render on every WhatsApp client.
// tombol
await sock.richMenu(jid, {
header: { title: 'Main Menu', image: { url: 'https://example.com/banner.png' } },
body: { title: 'Pick one', buttons: ['Profile', 'Settings', 'Help'], toast: 'opening...' },
footer: { text: 'Join us', url: 't.me/example' }
})
// kartu geser (carousel: true) / baris (row: true)
await sock.richMenu(jid, {
body: {
carousel: true,
cards: [
{ title: 'Card 1', buttons: ['A', 'B'], toast: '...' },
{ title: 'Card 2', buttons: ['C', 'D'], toast: '...' }
]
}
})footer.url is required for the open-URL button (no default link).
A2UI Widget (new in 10.5.0, risk-aware)
Declarative widgets (Text, Image, Video, Button, Card, Column, Row, Divider, CheckBox, TextField, ChoicePicker, plus listCard) sent as interactiveMessage.bloksWidget. Also exported as A2UI / sendA2UIWidget from lib/Utils/a2ui.js. The proto now includes InteractiveMessage.BloksWidget (field 8). Internal WhatsApp format, so it may not render on every client.
import { A2UI } from '@rennzsync/baileys'
const ui = new A2UI()
const title = ui.text('Halo!', { variant: 'h1' })
const label = ui.text('Klik saya')
const btn = ui.button(label, { action: { name: 'noop' } })
ui.root([ui.card(ui.column([title, btn]))])
await sock.sendA2UI(jid, { a2ui: ui, bodyText: 'Widget', footer: 'A2UI' })
// list card
const list = new A2UI().listCard({
title: 'Menu',
items: [{ title: 'Nasi Goreng', price: 'Rp15.000' }, { title: 'Es Teh', price: 'Rp5.000' }]
})
await sock.sendA2UI(jid, { a2ui: list, bodyText: 'Pesan menu' })Options: singleScreen, buttons (native flow { name, params }), expiration, contextInfo, quoted, wrapped, type.
relayMessage = Baileys original + shortcut layer (new in 10.6.1)
In 10.6.1 relayMessage is the original Baileys 7.0.0-rc14 implementation again (relayMessageCore, byte-for-byte upstream apart from one 3-line recipientFilter hook). Every fork feature now lives in a thin wrapper around it, and the wrapper is what sock.relayMessage exposes — sendMessage, retries, richMenu and sendA2UI all go through it.
Shortcut flags (same as before, now outside the core):
await sock.sendMessage(jid, content, { isSecret: true }) // only the recipient's primary device
await sock.sendMessage(jid, content, { protected: true }) // recipient primary + own devices
await sock.sendMessage(jid, content, { me: true }) // own devices onlyAuto <biz> node for button / list / native-flow messages (skipped if you pass your own biz node in additionalNodes, and for status / newsletter). Detection now checks every native-flow button, not only the first one, and unwraps viewOnce*:
| Button / message | <biz> sent |
| --- | --- |
| listMessage | <list type="product_list" v="2"/> (changed in 10.6.1, was native-flow mixed) |
| buttonsMessage, other native-flow | native_flow mixed |
| cta_catalog, mpm, send_location, view_catalog, wa_pay_detail | matching native_flow name |
| call_request | call_permission_request |
| review_and_pay, review_order, payment_info, payment_status, payment_method | native_flow_name order/payment node |
FLOW_NAME and ORDER_RESPONSE_NAME are now exported from lib/WABinary/generic-utils.js.
Stanza type / mediatype — ported from zapo-js 1.9.0:
- Wrapped content is resolved through all wrappers (
ephemeral,viewOnce*,documentWithCaption,edited,groupStatus/groupStatusV2,groupMentioned,botInvoke,botForwarded,deviceSent). Upstream callsgetMediaTypeon the raw message, so a wrapped image (view-once, group status, bot-forwarded) left astype=mediawith nomediatype, a pairing real clients never send. - New
mediatypevalues:location(andlivelocationviaisLive),ptv,sticker_pack,group_history. type=eventalso forencEventResponseMessageandsecretEncryptedMessage(EVENT_EDIT);type=pollalso forpollCreationMessageV5/V6.
Risk note: stanza shape is validated server-side. The
<list>biz node and the newmediatypevalues follow WhatsApp Web / zapo-js, but were not tested against a live account here — test on a throwaway number first.
Default Configuration (RAM-friendly)
const sock = makeWASocket({
auth: state,
// already frugal by default; override if needed:
syncFullHistory: false, // don't pull full chat history
enableRecentMessageCache: false, // don't keep recent messages in RAM
});Breaking Changes from 9.x (legacy renzsync-baileys)
- Base rebased to Baileys 7.0.0-rc14
- Removed modules:
lib/VoIP/*(WebRTC call client),Modded/message_builder.js,Utils/rich-messages.js,Socket/dugong.js,Utils/sticker-pack.js.rejectCallremains available (coremessages-recv).- Replacement for the old rich messages:
rich-webui.js(sendInlineWebUI,buildWebuiMessage).
- 10.0.1: added
rich-classic.js(buildButtonsMessage,buildListMessage,sendClassicMessage,normalizeUserJid) —interactiveMessage/nativeFlowMessagecards no longer render on many clients; use the classic templates for maximum compatibility. - 10.0.2: restored the ob9 auto-inject of the
<biz>stanza node (ported from ourin-baileys 9.0.21) —relayMessagenow automatically attaches thebizinteractive node forbuttonsMessage/listMessage/interactiveMessage+nativeFlowMessagepayloads, unless the caller already provides one. Without this node the server accepts the stanza but the receiving client never renders the card (relay succeeds silently, message never appears). This regressed during the rebase to Baileys 7 and was the root cause of invisible button/list menus. - 10.0.3: added
rich-carousel.js(buildCarouselMessage,buildInteractiveMessage,buildInteractiveCard,buildNativeFlowButton,sendCarouselMessage,sendInteractiveMessage) for carousel cards and richer native-flow CTA buttons (cta_url/cta_call/cta_copy/cta_reminder/single_select). Also extendedgenerateProfilePicture(andupdateProfilePicture/newsletterUpdatePicture) with a{ full: true }option to upload profile pictures at source resolution/high quality instead of the forced 640×640 quality-50 downscale. - 10.1.0: merged in
lib/Store/*(in-memory store, cache-manager store, keyed-db/ordered-dictionary/object-repository) from@vansnowi/baileys, plususeSqliteAuthState(Node 22.5+ built-innode:sqlite, with a clear fallback error on older Node) andisSecret/protected/me-only send filters wired intosock.sendMessage. - 10.2.0: added
generateWAMessageFromImagePoll/hashImagePollOption— client-side builder for WhatsApp's image-poll message type (pollCreationMessageV3+pollCreationOptionImageMessage,MEDIA_POLLassociation). Experimental — no upstream Baileys fork ships this. - 10.5.0: added
viewOnceV2/viewOnceV2Extensionsend options (wrap the message inviewOnceMessageV2/viewOnceMessageV2Extension; text getsviewOnce: trueinsideextendedTextMessage);sock.richMenu(rich-menu.js:buildRichMenuMessage,sendRichMenu— buttons, carousel/row cards, image header, open-URL footer) ported from@vansnowi/baileyswithout its hardcoded default footer link; A2UI widgets (a2ui.js:A2UI,sendA2UIWidget,sock.sendA2UI) sent viainteractiveMessage.bloksWidget, withInteractiveMessage.BloksWidget(field 8) added to WAProto (WAProto.proto,index.js,index.d.ts).richMenuand A2UI use internal WhatsApp formats and may not render on every client. - 10.7.0: WAProto replaced with a newer generated
WAProto/index.js(361 top-level types, up from 202) — adds the new bot/AI, Signal-key-derivation, backup and consumer-application messages.LIDMigrationMappingandLIDMigrationMappingSyncPayloadwere missing from it and were restored from Baileys rc14 (process-message.jsneeds them for LID mapping sync).WAProto.proto/index.d.tswere not regenerated — do not runnpm run build:proto, it would overwriteindex.jswith the older proto.InteractiveMessage.BloksWidget(A2UI) is still present. messages-send: removed thenoSelfSyncsend option and itsREADME.noselfsync.md; the rest of the shortcut layer (isSecret/protected/me, auto<biz>, zapo-jstype/mediatype,richMenu,sendA2UI) is unchanged andrelayMessageCorestays identical to Baileys 7.0.0-rc14 apart from therecipientFilterhook. Removed the stalemessages-send.js.map. - 10.6.1:
relayMessagerestored to the original Baileys 7.0.0-rc14 body (relayMessageCore+ arecipientFilterhook);isSecret/protected/meand the auto<biz>node moved to a wrapper thatsock.relayMessageexposes.<biz>detection checks every native-flow button and coverscta_catalog,mpm,call_request,view_catalog,wa_pay_detail,send_location;listMessagenow sends<list type="product_list" v="2"/>instead of native-flowmixed. Ported from zapo-js 1.9.0: stanzatype/mediatyperesolve through all wrappers (getMediaTypenow unwraps itself), newmediatypelocation/ptv/sticker_pack/group_history,type=event/type=pollfor the newer event & poll messages.FLOW_NAME/ORDER_RESPONSE_NAMEexported fromgeneric-utils.js. - 10.5.2: added
noSelfSyncsend option (sock.sendMessage(jid, content, { noSelfSync: true })) — skips the sender's own devices when encrypting a 1:1 message, so it is not synced to / visible on the sender device. Also in 10.5.2:generateWAMessageFromImagePollnow sends the<meta polltype="creation"/>node with the poll message (the poll itself wasn't rendering, only its option images); image thumbnails now work with jimp 1.x alone (the jimp branch checkedtypeof Jimp === 'object'and never ran, so a jimp-only setup had no thumbnails); the image library (sharp, else jimp^1.6.1) is resolved once and jimp is no longer imported when sharp is present (BAILEYS_IMAGE_LIB=jimpforces jimp); the video-thumbnail ffmpeg call usesexecFile(no shell, paths with spaces work). Note: thenoSelfSyncoption was removed again in 10.7.0. - Default config changed:
syncFullHistoryandenableRecentMessageCacheare nowfalse. protobufjs-clipinned to^1.1.3(peer dependency conflict fix);link-preview-jsto^5.0.0(SSRF advisory fix).- Rebrand: package renamed to
@rennzsync/baileys, now maintained by RennZz-Dev. No API changes — update your imports fromonigisto@rennzsync/baileys. - Signal engine reverted to stock: dropped the custom
@rennzsync/signal/@rennzsync/curve25519(MIT, native Rust) engine and went back to the originallibsignal(GPL-3.0) used by stock Baileys rc14 — same Signal Protocol implementation, no more custom native binaries to build/ship. See the license note near the top of this file.
Testing
npm test Includes unit tests for: JID utils (PN/LID/hosted), Rich WebUI (build + proto encode/decode roundtrip).
Credits
- RennZz-Dev —
rennzsync/baileysmaintainer: rebrand, ongoing upkeep & bot-focused tweaks - vinikjkkj/zapo — reference for the WhatsApp Web stanza shape (
type/mediatype/<biz>) ported in 10.6.1 - WhiskeySockets/Baileys — upstream library & original Signal Protocol wrapper (
libsignal-based)
License
MIT for rennzsync/baileys's own code — but it depends on libsignal (GPL-3.0) at runtime for Signal Protocol crypto. Check what that means for your use case before redistributing.
