WABotJS is built around a small set of well-defined layers that each own exactly one responsibility. Your application code talks only toDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/jzszdznzzl/WABotJS/llms.txt
Use this file to discover all available pages before exploring further.
Bot; everything below that boundary — the WebSocket connection, credential persistence, JID mapping, and message caching — is managed internally. This page walks through every layer from top to bottom so you know where each concern lives and how the pieces wire together at runtime.
Component diagram
datadir you pass to new Bot(id, datadir). Nothing is written outside that directory.
Bot
Bot is the single entry point for your application. You construct one instance, register event handlers, and call .login(). Internally, Bot:
- Resolves the
datadirto an absolute path and constructsAuth,Stores.JID,Stores.Message, and aTTLCache<GroupMetadata>. - Calls
Auth.load()then creates aSocketwith the loaded credentials. - Calls
.bind(sock)on both the JID and Message stores so they start listening to socket events. - Wires
creds.update,connection.update, andmessages.upsertlisteners. - Exposes chainable handler setters:
.onError(),.onQR(),.onOTP(),.onOpen(),.onClose(),.onMessage(), and.onCommand().
Bot also exposes .close() (drops the socket without clearing credentials) and .logout() (drops the socket and wipes credentials from disk).
Socket
Socket is a thin, typed wrapper around baileys.makeWASocket. When constructed it spreads all Baileys socket properties onto itself (giving you the full Baileys API surface), then overrides end and logout with typed async methods:
| Method | Signature | Description |
|---|---|---|
end | (reason?: Error) => Promise<void> | Gracefully closes the WebSocket. |
logout | (reason?: Error) => Promise<void> | Deregisters the device from WhatsApp, then closes. |
Socket yourself — Bot creates it during .login(). You can access the live socket via bot.sock for any Baileys API call (sending messages, fetching group metadata, etc.) after the connection is open.
Accessing
bot.sock before calling bot.login() throws an error. Always wait for the onOpen callback before using socket methods.Auth
Auth owns all credential and signal-key storage. It writes to a single SQLite file (auth.sqlite) inside your datadir using Node.js’s built-in node:sqlite module. Key design decisions:
WAL journal mode
The database is opened with
PRAGMA journal_mode = WAL so reads never block writes.NORMAL synchronous
PRAGMA synchronous = NORMAL balances durability with write performance on modern file systems.Atomic key updates
Signal key batch writes are wrapped in an explicit
BEGIN TRANSACTION … COMMIT block (with ROLLBACK on error) so partial updates never corrupt the key store.Baileys BufferJSON
Credentials and keys are serialised with
baileys.BufferJSON.replacer/reviver so binary data round-trips correctly through JSON → UTF-8 bytes → SQLite BLOB..login(). If no existing credentials are found, baileys.initAuthCreds() produces a fresh set. On every creds.update event the bot calls auth.saveCreds() to persist the latest credentials.
Stores
WABotJS ships two persistent stores, both built onUtils.SQLiteStore:
JID Store (cache.jid)
Stores.JID maintains a bidirectional mapping between LID JIDs (@lid) and phone-number JIDs (@s.whatsapp.net) in jid_store.sqlite. Mappings are populated automatically from:
messages.upsertevents — when a message carries bothremoteJidandremoteJidAlt(orparticipantandparticipantAlt), the pair is stored.connection.update(open) events — the bot’s ownuser.id/user.lidpair is always stored on connect.
Message Store (cache.message)
Stores.Message stores serialised WAProto.IMessage objects keyed by message ID in message_store.sqlite. It has a 5-day TTL (432,000,000 ms) and is used exclusively by Baileys’ getMessage callback, which Baileys calls when it needs to re-decrypt a message during a retry or history sync.
Buffer and Uint8Array values are serialised with a custom JSON replacer that converts them to tagged plain-object form ({ type: 'Buffer', data: [...] }) so they survive JSON round-trips through the UTF-8 BLOB column.
Cache layers
EverySQLiteStore instance pairs an in-memory TTLCache (L1) with SQLite (L2):
set(key, value) the value is written to both L1 and L2 atomically. On del(key) it is removed from both. This means repeated reads of recently-written keys (e.g. auth keys refreshed on every message) never touch the disk.
The L1 TTLCache uses a background setInterval cleaner (unref’d so it does not prevent process exit) that sweeps expired entries every ttl milliseconds. When the cache becomes empty the interval is stopped automatically.
Reconnection strategy
When the socket closes with a non-permanent error code,Bot reconnects with exponential back-off:
| Parameter | Value |
|---|---|
| Initial delay | 5,000 ms (5 s) |
| Growth factor | ×2 on each failure |
| Maximum delay | 300,000 ms (5 min) |
Bot calls onClose and then logout() instead of retrying:
loggedOut
The account explicitly logged out this device.
forbidden
WhatsApp rejected the session (banned or policy violation).
405
Method not allowed — session is no longer valid.
400
Bad request — malformed session state.
restartRequired is handled as a special non-delayed case: the bot calls .login() immediately without incrementing the retry delay.
Event flow
Understanding how events travel through the stack helps you reason about the order in which your callbacks fire.messages.upsert — incoming messages and commands
messages.upsert — incoming messages and commands
- Baileys emits
messages.upsertwithtype: 'notify'. Stores.JIDinspects each message forremoteJidAlt/participantAltpairs and writes any new LID↔PN mappings to SQLite.Stores.Messagestores each message’sWAProto.IMessagepayload in SQLite.Botwraps the raw Baileys message in aMessageinstance and calls youronMessagehandler.- If the message text begins with the configured prefix,
Botparses the command name and arguments, then calls youronCommandhandler.
.catch()-chained into onError so a throw inside your handler does not crash the process.connection.update — login and reconnect lifecycle
connection.update — login and reconnect lifecycle
- When
u.qris set and a phone number was supplied to.login(pn)but the device is not yet registered, Bot callssock.requestPairingCode(pn)and delivers the code to youronOTPhandler. Otherwise it delivers the raw QR string toonQR. - When
u.connection === 'close', Bot inspectsu.lastDisconnect.erroras a Boom status code. Permanent codes triggeronClose+logout(); all others trigger a delayed or immediate.login()retry. - When
u.connection === 'open',Stores.JIDstores the bot’s own LID↔PN pair, the retry delay resets to 5 s, and youronOpenhandler fires.
creds.update — credential persistence
creds.update — credential persistence
Baileys emits
creds.update whenever the authentication state changes (e.g. after pairing, after a key rotation). Bot immediately calls auth.saveCreds(), which serialises the current AuthenticationCreds to auth.sqlite. Signal keys are updated in the keys.set callback wrapped in a transaction.