Panduan lengkap bikin bot WhatsApp dengan @mataram/wa — dari instalasi sampe pairing code.
Butuh Node.js minimal versi 20 (udah dicek sama engines di package.json).
npm install @mataram/wa
Atau pake yarn:
yarn add @mataram/wa
useMultiFileAuthState adalah fungsi yang nyimpen session ke folder di disk. Setiap kali ada perubahan di creds.update, lu harus panggil saveCreds biar data session (key Signal, token pairing, dll) tersimpan.
| Parameter | Tipe | Keterangan |
|---|---|---|
| folder | string | Nama folder buat nyimpen auth. Contoh: 'auth' |
| Property | Tipe | Keterangan |
|---|---|---|
| state | AuthenticationState | Berisi creds (data login) & keys (Signal keys) |
| saveCreds | () => Promise<void> | Callback buat nyimpen creds setiap ada update |
import makeWASocket, { useMultiFileAuthState, Browsers, DisconnectReason } from '@mataram/wa' const { state, saveCreds } = await useMultiFileAuthState('auth') sock.ev.on('creds.update', saveCreds)
Folder auth/ bakal berisi file-file session. Jangan pernah commit folder ini ke git. Isinya: creds.json, pre-key, session, sender-key, app-state-sync-key, dll.
| Option | Tipe | Default | Keterangan |
|---|---|---|---|
auth | AuthenticationState | (wajib) | State auth dari useMultiFileAuthState |
browser | [string, string, string] | macOS Chrome | Identitas browser. WhatsApp reject kalo nama browser aneh |
waWebSocketUrl | string | URL | wss://web.whatsapp.com/ws/chat | URL WebSocket WA |
connectTimeoutMs | number | 20000 | Timeout koneksi (ms) |
defaultQueryTimeoutMs | number | undefined | 60000 | Timeout default buat query (ms) |
keepAliveIntervalMs | number | 30000 | Ping interval ke server (ms) |
logger | ILogger | pino child | Logger pino-compatible |
emitOwnEvents | boolean | true | Emit event untuk aksi yang dilakukan socket sendiri |
markOnlineOnConnect | boolean | true | Tandai online pas konek |
syncFullHistory | boolean | true | Sinkronisasi history penuh |
fireInitQueries | boolean | true | Jalankan query inisialisasi otomatis |
countryCode | string | 'US' | Kode negara (USA -> US) |
linkPreviewImageThumbnailWidth | number | 192 | Lebar thumbnail link preview |
generateHighQualityLinkPreview | boolean | false | Hasilkan link preview kualitas tinggi (upload thumbnail ke WA) |
maxMsgRetryCount | number | 5 | Maks percobaan ulang pesan gagal |
enableRecentMessageCache | boolean | true | Cache pesan terbaru buat retry |
getMessage | (key) => Promise<proto.IMessage | undefined> | async () => undefined | Ambil pesan dari database lu buat retry |
cachedGroupMetadata | (jid) => Promise<GroupMetadata | undefined> | async () => undefined | Cache metadata grup biar ga repeated request |
shouldSyncHistoryMessage | (msg) => boolean | skip FULL | Filter tipe history sync yang diproses |
agent | Agent | undefined | Proxy agent untuk koneksi WS (contoh: SocksProxyAgent) |
fetchAgent | Agent | undefined | Proxy agent untuk fetch request (upload/download media) |
WA cuma nerima browser tertentu. Jangan asal ngasih string random. Pake helper dari Browsers:
| Fungsi | Contoh | Hasil |
|---|---|---|
Browsers.macOS(name) | Browsers.macOS('Chrome') | ['Chrome (Mac OS)', 'Chrome', '10.15.7'] |
Browsers.windows(name) | Browsers.windows('Edge') | ['Edge (Windows)', 'Edge', '10.0'] |
Browsers.ubuntu(name) | Browsers.ubuntu('Firefox') | ['Firefox (Ubuntu)', 'Firefox', '22.04'] |
Browsers.mataram(name) | Browsers.mataram('App') | ['App (Mac OS)', 'App', '1.0.0'] |
Browsers.appropriate(name) | Browsers.appropriate('Bot') | Deteksi OS dari process.platform |
const sock = makeWASocket({ auth: state, browser: Browsers.macOS('Chrome'), emitOwnEvents: true, markOnlineOnConnect: true, syncFullHistory: true, })
| Property | Tipe | Keterangan |
|---|---|---|
connection | 'open' | 'connecting' | 'close' | Status koneksi saat ini |
lastDisconnect | { error: Boom | Error, date: Date } | Error terakhir yang nutup koneksi. Cek error.output.statusCode |
isNewLogin | boolean | Apakah ini login baru (baru pairing) |
qr | string | undefined | QR code string (Base64) — kalau pake QR pairing |
receivedPendingNotifications | boolean | undefined | Udah nerima notifikasi offline apa belum |
isOnline | boolean | undefined | Apakah ditandai online di WA |
reachoutTimeLock | ReachoutTimelockState | Info pembatasan akun (kalo kena restrict) |
sock.ev.on('connection.update', async ({ connection, lastDisconnect, qr }) => { if (qr) { console.log('Scan QR ini:', qr) } if (connection === 'open') { console.log('Nyambung!', sock.user?.id) } if (connection === 'close') { const code = lastDisconnect?.error?.output?.statusCode if (code === DisconnectReason.loggedOut) { console.log('Session expired. Hapus folder auth dan pairing ulang.') } else { setTimeout(start, 3000) } } })
connecting — Socket baru connect, handshake Noise Protocolconnecting dengan receivedPendingNotifications: false — Nunggu notifikasi offlineCB:ib,,offline — tandanya semua notifikasi offline udah dikirimreceivedPendingNotifications: true — Siap masuk ke sync phaseopen — Koneksi siap dipake. Event udah di-flushclose — Koneksi putus. Cek lastDisconnect.error buat reconnect logicCreds berisi semua data krusial: key Signal (identity, pre-key, signed pre-key), pairing code, LID mapping, account sync state. Kalo ga disave, tiap restart harus pairing ulang. Makanya selalu pasang listener ini:
sock.ev.on('creds.update', saveCreds)
saveCreds adalah function dari useMultiFileAuthState yang nulis ulang file auth/creds.json. Ini bakal kepanggil otomatis pas pairing, pas ada update key, pas sync selesai, dll.
| Parameter | Tipe | Keterangan |
|---|---|---|
phoneNumber | string | Nomor HP tujuan. Format: 6281234567890 (tanpa +, tanpa spasi) |
customPairingCode (opsional) | string | Kode pairing kustom, harus PERSIS 8 karakter. Kalo dikasih, WA bakal nampilin kode ini. |
Balikannya string — kode pairing yang harus diketik di WhatsApp → Linked Devices → Pair a device.
if (!state.creds.registered) { const code = await sock.requestPairingCode('6281234567890') console.log('Kode pairing (8 digit):', code) }
Lu bisa tentuin sendiri kode pairingnya (wajib 8 karakter):
const code = await sock.requestPairingCode('6281234567890', 'MATARAM')
Catatan: requestPairingCode bakal ngeset creds.me dengan ID user dan emit creds.update. Setelah pairing berhasil, server bakal restart koneksi (stream error 515) — itu normal.
JID adalah alamat unik tiap entitas di WhatsApp. Formatnya:
| Tipe | Format JID | Contoh |
|---|---|---|
| User (Phone Number) | nomor@s.whatsapp.net | 6281234567890@s.whatsapp.net |
| User (LID) | nomor@lid | 1234567890@lid |
| Grup | id@g.us | 1234567890-123456@g.us |
| Newsletter | id@newsletter | 1234567890@newsletter |
| Status Broadcast | status@broadcast | status@broadcast |
| PSA | 0@broadcast | 0@broadcast |
Ada juga device suffix: 628xxx@s.whatsapp.net.0 (device 0 = primary). Fungsi helper yang disediain:
import { jidDecode, jidNormalizedUser, areJidsSameUser, isJidUser, isJidGroup } from '@mataram/wa' jidDecode('6281234567890@s.whatsapp.net') // { user: '6281234567890', server: 's.whatsapp.net', device: undefined, agent: undefined } jidNormalizedUser('6281234567890@s.whatsapp.net.0') // '6281234567890@s.whatsapp.net' — hapus device suffix areJidsSameUser('6281234567890@s.whatsapp.net', '6281234567890@s.whatsapp.net.3') // true — user yang sama, device beda
MessageKey adalah "alamat" unik sebuah pesan. Setiap pesan yang diterima atau dikirim punya ini:
| Property | Tipe | Keterangan |
|---|---|---|
id | string | ID unik pesan (format: BAE5xxxx atau 3EBxxxx) |
remoteJid | string | JID chat tempat pesan berada |
fromMe | boolean | Apakah pesan dari kita sendiri |
participant | string | undefined | Di grup: JID pengirim. Di chat pribadi: undefined |
Cara dapetin:
sock.ev.on('messages.upsert', ({ messages }) => { const msg = messages[0] const key = msg.key console.log(key.id, key.remoteJid, key.fromMe, key.participant) })
MessageKey dipake di sendMessage buat reply, reaction, edit, delete, pin, forward. Simpen aja key-nya pas nerima pesan, tinggal dipake kapan aja.
import makeWASocket, { useMultiFileAuthState, Browsers, DisconnectReason } from '@mataram/wa' const start = async () => { const { state, saveCreds } = await useMultiFileAuthState('auth') const sock = makeWASocket({ auth: state, browser: Browsers.macOS('Chrome'), getMessage: async key => undefined, }) sock.ev.on('creds.update', saveCreds) sock.ev.on('connection.update', async ({ connection, lastDisconnect, qr }) => { if (qr) console.log('QR:', qr) if (connection === 'open') console.log('Nyambung sebagai', sock.user?.id) if (connection === 'close') { const code = lastDisconnect?.error?.output?.statusCode const shouldReconnect = code !== DisconnectReason.loggedOut if (shouldReconnect) start() } }) if (!state.creds.registered) { const code = await sock.requestPairingCode('6281234567890') console.log('Kode:', code) } sock.ev.on('messages.upsert', async ({ messages, type }) => { if (type !== 'notify') return const msg = messages[0] if (msg.key.fromMe) return console.log('Pesan dari', msg.key.remoteJid, msg.message?.conversation) }) } start()