Documentation Index Fetch the complete documentation index at: https://zapo.to/llms.txt
Use this file to discover all available pages before exploring further.
This guide builds a minimal “ping → pong” bot. It connects, prints a QR code to pair, and replies pong whenever it receives ping.
Install the packages
npm install zapo-js @zapo-js/store-sqlite better-sqlite3 pino pino-pretty
Create the store and client
The store persists auth and Signal state. Here we use SQLite, writing to .auth/state.sqlite. import { createPinoLogger , createStore , WaClient } from 'zapo-js'
import { createSqliteStore } from '@zapo-js/store-sqlite'
const logger = await createPinoLogger ({ level: 'info' , pretty: true })
const store = createStore ({
backends: {
sqlite: createSqliteStore ({ path: '.auth/state.sqlite' , driver: 'auto' })
},
providers: {
auth: 'sqlite' ,
signal: 'sqlite' ,
senderKey: 'sqlite' ,
appState: 'sqlite' ,
messages: 'sqlite' ,
threads: 'sqlite' ,
contacts: 'sqlite' ,
privacyToken: 'sqlite'
}
})
const client = new WaClient (
{
store ,
sessionId: 'default' ,
connectTimeoutMs: 15_000 ,
nodeQueryTimeoutMs: 30_000 ,
history: { enabled: true , requireFullSync: true }
},
logger
)
Handle pairing
On a fresh session the client emits auth_qr. Render the value as a QR code (for example with the qrcode-terminal package) and scan it from WhatsApp → Linked devices . client . on ( 'auth_qr' , ({ qr , ttlMs }) => {
console . log ( 'Scan this QR within' , ttlMs , 'ms:' )
console . log ( qr )
})
client . on ( 'auth_paired' , ({ credentials }) => {
console . log ( 'Paired as' , credentials . meJid )
})
Reply to incoming messages
Listen for the message event and send a reply with client.message.send. function extractText ( message ) {
return (
message ?. conversation ??
message ?. extendedTextMessage ?. text ??
undefined
)
}
client . on ( 'message' , async ( event ) => {
const text = extractText ( event . message )
if ( text ?. trim (). toLowerCase () !== 'ping' ) return
const to = event . chatJid ?? event . senderJid
if ( ! to ) return
await client . message . send ( to , 'pong' )
})
Connect
connect() resolves once the socket is open. The first connection drives pairing; subsequent connections reuse the stored credentials.
Full example
import { createPinoLogger , createStore , WaClient } from 'zapo-js'
import { createSqliteStore } from '@zapo-js/store-sqlite'
const logger = await createPinoLogger ({ level: 'info' , pretty: true })
const store = createStore ({
backends: {
sqlite: createSqliteStore ({ path: '.auth/state.sqlite' , driver: 'auto' })
},
providers: {
auth: 'sqlite' ,
signal: 'sqlite' ,
senderKey: 'sqlite' ,
appState: 'sqlite' ,
messages: 'sqlite' ,
threads: 'sqlite' ,
contacts: 'sqlite' ,
privacyToken: 'sqlite'
}
})
const client = new WaClient ({ store , sessionId: 'default' }, logger )
client . on ( 'auth_qr' , ({ qr }) => console . log ( qr ))
client . on ( 'connection' , ( event ) => console . log ( 'connection:' , event . status , event . reason ))
client . on ( 'message' , async ( event ) => {
const text =
event . message ?. conversation ?? event . message ?. extendedTextMessage ?. text
if ( text ?. trim (). toLowerCase () !== 'ping' ) return
const to = event . chatJid ?? event . senderJid
if ( to ) await client . message . send ( to , 'pong' )
})
await client . connect ()
What’s next
Sending messages Replies, mentions, link previews, and the full content union.
Receiving messages Parse incoming events, send receipts, and decrypt addons.
Reconnection zapo does not auto-reconnect — here’s the pattern to handle it.
Configuration Sessions, history sync, timeouts, proxy, and more.