TypeScript library for WhatsApp Web API
Pastikan Node.js versi 20 atau lebih baru sudah terinstal. Jalankan perintah berikut di terminal project kamu:
npm install @mataram/wa
Library ini mendukung TypeScript secara langsung. Semua type definitions sudah termasuk.
Untuk terhubung ke WhatsApp, kamu perlu mengikuti langkah-langkah berikut secara berurutan.
Buka file index.js (atau index.ts kalo pake TypeScript). Tulis kode berikut di bagian atas:
import makeWASocket, { useMultiFileAuthState, Browsers } from '@mataram/wa'
makeWASocket adalah fungsi utama untuk membuat koneksi. useMultiFileAuthState untuk menyimpan session ke file. Browsers untuk mengatur identitas browser.
Session WhatsApp disimpan di folder. Fungsi useMultiFileAuthState akan membuat folder dan menyimpan credentials ke dalamnya.
const { state, saveCreds } = await useMultiFileAuthState('auth_info')
Folder auth_info akan berisi file-file session. Penting: folder ini JANGAN dihapus dan JANGAN di-commit ke git. Kalo ilang, kamu harus pairing ulang.
Fungsi makeWASocket menerima object konfigurasi. Parameter auth wajib diisi, browser juga penting.
const sock = makeWASocket({ auth: state, browser: Browsers.macOS('Chrome'), })
Parameter browser harus menggunakan browser asli. WhatsApp akan menolak koneksi dari browser palsu. Pilihan yang aman:
Browsers.macOS('Chrome') — pake platform Mac OSBrowsers.windows('Edge') — pake platform WindowsBrowsers.ubuntu('Firefox') — pake platform UbuntuSetiap kali credentials berubah (misal: setelah pairing), kita harus menyimpannya. Event creds.update akan dipanggil otomatis oleh library.
sock.ev.on('creds.update', saveCreds)
Baris ini WAJIB ada. Tanpa ini, session tidak akan tersimpan dan kamu harus pairing setiap restart.
Event connection.update memberikan informasi tentang status koneksi. Ada tiga status: connecting (menghubungkan), open (terhubung), close (terputus).
sock.ev.on('connection.update', async ({ connection, lastDisconnect }) => { if (connection === 'open') { // Koneksi berhasil. sock.user.id berisi nomor WhatsApp kamu. console.log('Terhubung sebagai', sock.user?.id) } if (connection === 'close') { const code = lastDisconnect?.error?.output?.statusCode if (code === 401) { // Session expired. Hapus folder auth_info dan pairing ulang. console.log('Session tidak valid') } else { // Koneksi terputus. Coba reconnect otomatis. setTimeout(start, 3000) } } })
Kode error:
401 — Logged Out: session expired, harus pairing ulang408 — Timeout: koneksi terlalu lama428 — Connection Closed: koneksi ditutup (bisa dicoba ulang)515 — Restart Required: normal setelah pairing, socket akan reconnect otomatisPairing code adalah metode untuk menghubungkan bot ke WhatsApp tanpa scan QR. Kamu cukup memasukkan kode di HP.
if (!state.creds.registered) { // Nomor HP dengan kode negara (628 untuk Indonesia), tanpa +, tanpa spasi const code = await sock.requestPairingCode('6281234567890') console.log('Kode pairing:', code) // Ketik kode ini di HP: WhatsApp > Perangkat Tertaut > Tautkan Perangkat }
Kamu bisa menentukan sendiri kode pairing (maksimal 8 karakter huruf/angka). Parameter kedua dari fungsi requestPairingCode.
const code = await sock.requestPairingCode('6281234567890', 'MATARAM') // Pairing code: MATARAM
Kode kustom berguna untuk branding atau memudahkan pengguna mengetik kode.
JID adalah alamat unik setiap entitas di WhatsApp. Formatnya: nama@domain.
| Tipe | Format | Contoh |
|---|---|---|
| Personal | nomor@s.whatsapp.net | 6281234567890@s.whatsapp.net |
| Grup | id@g.us | 123456789-12345@g.us |
| Channel | id@newsletter | 120363...@newsletter |
Untuk nomor pribadi: gunakan kode negara tanpa +. Contoh: 628xxx (Indonesia). Jangan gunakan 08xxx.
messageKey adalah object yang mengidentifikasi sebuah pesan secara unik. Strukturnya:
{
remoteJid: '628xxx@s.whatsapp.net', // JID pengirim/penerima
fromMe: true, // true kalo dari kita
id: 'BAE5AF1E3C0D4A2B' // ID unik pesan
}
MessageKey diperlukan untuk operasi seperti reply, reaction, edit, delete.
Saat ada pesan masuk, semua informasi tersedia di event messages.upsert:
sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { const jid = msg.key.remoteJid // JID pengirim const key = msg.key // MessageKey lengkap const text = msg.message?.conversation // Isi pesan text const fromMe = msg.key.fromMe // true kalo dari kita sendiri } })
Semua jenis pesan dikirim melalui satu fungsi: sock.sendMessage(jid, content, options).
| Parameter | Wajib | Jenis | Keterangan |
|---|---|---|---|
jid | Ya | string | Tujuan pesan (personal, grup, atau channel) |
content | Ya | object | Isi pesan (text, image, video, dll) |
options | Tidak | object | Opsi tambahan (quoted, ephemeral, dll) |
Mengirim pesan teks biasa. Parameter text adalah string yang akan dikirim.
await sock.sendMessage('6281234567890@s.whatsapp.net', { text: 'Halo! Ini pesan dari bot.' })
Membalas pesan tertentu. Parameter quoted di options diisi dengan object message yang ingin dibalas.
await sock.sendMessage(jid, { text: 'Ini balasan untuk pesan sebelumnya' }, { quoted: originalMessage })
Nilai originalMessage bisa didapat dari event messages.upsert. Cukup simpan object message yang diterima, lalu gunakan sebagai quoted.
Memberi reaksi emoji ke pesan. Parameter react.text berisi emoji. react.key adalah messageKey dari pesan yang ingin direaksi.
await sock.sendMessage(jid, { react: { text: '🔥', key: messageKey } }) // Untuk menghapus reaksi, kirim string kosong: await sock.sendMessage(jid, { react: { text: '', key: messageKey } })
Emoji bisa apa saja: 👍 ❤️ 😂 😮 😢 😡. Untuk hapus reaksi, gunakan text: '' (string kosong).
Menyebut pengguna dalam pesan grup. Parameter mentions berisi array JID yang ingin disebut. Format @nomor di teks bersifat opsional tapi disarankan.
await sock.sendMessage(jid, { text: 'Halo @6281234567890, selamat datang!', mentions: ['6281234567890@s.whatsapp.net'] })
Hanya berfungsi di grup. Mention di chat pribadi tidak diperlukan.
Mengirim gambar. Gambar bisa dari URL, file lokal, atau Buffer. Parameter caption opsional.
// Dari URL await sock.sendMessage(jid, { image: { url: 'https://example.com/foto.jpg' }, caption: 'Foto keren' }) // Dari file lokal await sock.sendMessage(jid, { image: fs.readFileSync('./foto.jpg'), caption: 'Foto lokal' })
Parameter ptv (false = video biasa, true = video note lingkaran).
await sock.sendMessage(jid, { video: { url: './video.mp4' }, caption: 'Video' }) // Video note (lingkaran, seperti di Instagram) await sock.sendMessage(jid, { video: { url: './video.mp4' }, ptv: true })
Audio bisa dikirim sebagai voice note (ptt: true) atau file audio biasa. mimetype harus sesuai.
// Voice note await sock.sendMessage(jid, { audio: { url: './audio.ogg' }, mimetype: 'audio/ogg; codecs=opus', ptt: true })
await sock.sendMessage(jid, { document: { url: './file.pdf' }, fileName: 'dokumen.pdf', mimetype: 'application/pdf' })
Sticker harus dalam format WEBP.
await sock.sendMessage(jid, { sticker: { url: './sticker.webp' } })
Mengirim polling. name adalah pertanyaan. values adalah array pilihan. selectableCount (1 = pilih satu, >1 = pilih banyak).
await sock.sendMessage(jid, { poll: { name: 'Framework favorit?', values: ['Next.js', 'React', 'Vue'], selectableCount: 1 } })
await sock.sendMessage(jid, { location: { degreesLatitude: -6.2088, // Latitude (contoh: Jakarta) degreesLongitude: 106.8456 // Longitude } })
Mengirim kontak berupa vCard. Format vCard standar.
const vcard = [ 'BEGIN:VCARD', 'VERSION:3.0', 'FN:Nama Kontak', 'TEL;type=CELL;waid=6281234567890:+62 812 3456 7890', 'END:VCARD' ].join('\n') await sock.sendMessage(jid, { contacts: { displayName: 'Nama Kontak', contacts: [{ vcard }] } })
Fungsi untuk memproses JID (WhatsApp ID).
import { jidDecode, jidEncode, jidNormalizedUser, areJidsSameUser, isJidGroup, isJidNewsletter, isJidUser } from '@mataram/wa' // Decode: '628xxx:0@s.whatsapp.net' → { user, server, device } const d = jidDecode('628xxx:0@s.whatsapp.net') // Encode: { user, server, device } → '628xxx:0@s.whatsapp.net' const jid = jidEncode('628xxx', 's.whatsapp.net', 0) // Normalisasi (hapus device): '628xxx:0@s.whatsapp.net' → '628xxx@s.whatsapp.net' const normal = jidNormalizedUser('628xxx:0@s.whatsapp.net') // Bandingkan (abaikan device suffix) areJidsSameUser('628xxx:0@s.whatsapp.net', '628xxx@s.whatsapp.net') // true // Cek tipe isJidGroup('123@g.us') isJidNewsletter('123@newsletter') isJidUser('628xxx@s.whatsapp.net')
Pesan yang hanya bisa dilihat sekali. Tambahkan viewOnce: true ke content.
await sock.sendMessage(jid, { image: { url: './foto.jpg' }, viewOnce: true })
Mengubah teks pesan yang sudah terkirim. Parameter edit berisi messageKey dari pesan yang ingin diedit.
await sock.sendMessage(jid, { text: 'Teks yang sudah diperbaiki', edit: messageKey })
Hanya bisa edit pesan text. Pesan media tidak bisa diedit.
Menghapus pesan untuk semua orang. Parameter delete berisi messageKey.
await sock.sendMessage(jid, { delete: messageKey })
Fitur ini mengubah teks markdown menjadi tampilan interaktif ala AI chat. Cukup tambahkan rich: true.
await sock.sendMessage(jid, { text: '# Judul\n\nText *bold* _italic_ ~coret~\n\nKode:\n\`\`\`javascript\nconst x = 1\nconsole.log(x)\n\`\`\`\n\nTabel:\n| Nama | Umur |\n|------|------|\n| Ana | 25 |\n\n\n\n:::suggest\nPilih 1 | Pilih 2\n:::', rich: true, })
Fitur markdown yang didukung:
# Heading — heading level 1-4*bold* — teks tebal_italic_ — teks miring~strikethrough~ — teks coret`code` — monospace``` ``` — code block dengan syntax highlighting| Tabel | — tabel markdown — gambar dari URL:::suggest — tombol saran interaktifTombol bawaan WhatsApp yang muncul di bawah pesan. Setiap tombol punya buttonId (ID unik) dan displayText (teks tombol).
await sock.sendMessage(jid, { text: 'Pilih menu:', buttons: [ { buttonId: 'produk', buttonText: { displayText: 'Produk' }, type: 1 }, { buttonId: 'pesanan', buttonText: { displayText: 'Pesanan' }, type: 1 }, { buttonId: 'bantuan', buttonText: { displayText: 'Bantuan' }, type: 1 }, ], })
Saat tombol ditekan, event messages.upsert akan menerima pesan dengan buttonsResponseMessage. Cek msg.message?.buttonsResponseMessage?.selectedButtonId untuk mendapatkan ID tombol yang ditekan.
Tiga jenis tombol: URL (buka link), Quick Reply (balas cepat), Call (telepon).
await sock.sendMessage(jid, { text: 'Aksi:', templateButtons: [ { urlButton: { displayText: 'Buka Website', url: 'https://example.com' } }, { quickReplyButton: { displayText: 'OK', id: 'ok' } }, { callButton: { displayText: 'Hubungi', phoneNumber: '+6281234567890' } }, ], })
Menu dropdown dengan kategori dan pilihan. Cocok untuk menu bertingkat.
await sock.sendMessage(jid, { text: 'Pilih kategori:', buttonText: 'Lihat', // Teks tombol dropdown sections: [{ title: 'Produk Digital', rows: [ { id: 'pulsa', title: 'Pulsa', description: 'Isi pulsa all operator' }, { id: 'data', title: 'Paket Data', description: 'Kuota internet' }, ], }, { title: 'Lainnya', rows: [ { id: 'bantuan', title: 'Bantuan', description: 'Hubungi CS' }, ], }], })
Membuat undangan event dengan tanggal, lokasi, dan tombol RSVP. Penerima bisa memilih Going / Not Going / Maybe.
await sock.sendMessage(jid, { event: { name: 'Meetup Mataram', description: 'Diskusi pengembangan library WhatsApp API', startDate: new Date('2025-07-01T10:00:00'), endDate: new Date('2025-07-01T12:00:00'), location: { degreesLatitude: -6.2, degreesLongitude: 106.8, name: 'Jakarta' }, } })
Mengirim beberapa gambar sekaligus yang tergabung dalam satu album. Gambar pertama sebagai header, sisanya menyusul.
const album = await sock.sendMessage(jid, { image: { url: './foto1.jpg' }, caption: 'Album liburan', album: { expectedImageCount: 3 }, }) await sock.sendMessage(jid, { image: { url: './foto2.jpg' }, albumParentKey: album.key }) await sock.sendMessage(jid, { image: { url: './foto3.jpg' }, albumParentKey: album.key })
Gambar pertama menentukan jumlah total gambar melalui expectedImageCount. Gambar berikutnya menggunakan albumParentKey yang merujuk ke key gambar pertama.
Selain pairing code, kamu juga bisa menggunakan QR code. QR code akan muncul di event connection.update sebagai qr (base64).
import { toBuffer } from 'qrcode' // npm install qrcode sock.ev.on('connection.update', async ({ qr }) => { if (qr) { // Tampilkan QR di terminal const terminal = await toBuffer(qr, { type: 'terminal', small: true }) console.log(terminal.toString()) } })
Pairing code lebih direkomendasikan karena lebih praktis. QR code hanya berguna kalo kamu bisa menampilkan gambar QR.
Meneruskan pesan dari satu chat ke chat lain. Gunakan properti forward.
// Forward pesan yang diterima ke chat lain sock.ev.on('messages.upsert', async ({ messages }) => { for (const msg of messages) { if (msg.key.fromMe) continue // Forward ke nomor sendiri await sock.sendMessage('628xxx@s.whatsapp.net', { forward: msg }) } })
Mengatur pesan sementara yang otomatis hilang setelah waktu tertentu.
// Atur disappearing mode di chat (dalam detik) await sock.sendMessage(jid, { disappearingMessagesInChat: 86400 }) // 24 jam await sock.sendMessage(jid, { disappearingMessagesInChat: 604800 }) // 7 hari await sock.sendMessage(jid, { disappearingMessagesInChat: 0 }) // Matikan // Kirim pesan yang hilang dalam 24 jam await sock.sendMessage(jid, { text: 'Pesan rahasia' }, { ephemeralExpiration: 86400 })
Library menggunakan EventEmitter. Kamu bisa mendengarkan berbagai event untuk merespon aksi yang terjadi.
Event ini memberikan informasi tentang status koneksi WebSocket ke WhatsApp.
| Property | Jenis | Keterangan |
|---|---|---|
| connection | string | 'connecting' | 'open' | 'close' |
| lastDisconnect | Boom | null | Error saat koneksi terputus |
| qr | string | undefined | QR code (base64) untuk autentikasi |
| isNewLogin | boolean | Apakah ini login baru |
sock.ev.on('connection.update', ({ connection, lastDisconnect, qr }) => { switch (connection) { case 'connecting': console.log('Menghubungkan ke WhatsApp...') break case 'open': console.log('Terhubung sebagai', sock.user?.id) break case 'close': const code = lastDisconnect?.error?.output?.statusCode console.log('Koneksi terputus:', code) break } })
Event ini dipanggil saat ada pesan baru masuk (real-time) atau saat sync history.
| Property | Jenis | Keterangan |
|---|---|---|
| messages | WAMessage[] | Array pesan yang diterima |
| type | string | 'notify' (real-time) | 'append' (history) | 'prepend' |
| requestId | string | undefined | ID request jika ini hasil dari fetch history |
sock.ev.on('messages.upsert', ({ messages, type }) => { for (const msg of messages) { // msg.key.remoteJid = JID pengirim // msg.key.fromMe = true jika dari bot sendiri // msg.key.id = ID unik pesan // msg.message = object berisi isi pesan // msg.messageTimestamp = timestamp if (msg.key.fromMe) continue // skip pesan dari bot sendiri // Cara mendapatkan teks pesan (handle berbagai tipe): const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text || msg.message?.imageMessage?.caption || msg.message?.videoMessage?.caption || '[non-text]' const sender = msg.key.participant || msg.key.remoteJid // JID pengirim console.log(`Pesan dari ${sender}: ${text}`) } })
Catatan: Selalu cek msg.key.fromMe untuk menghindari loop (bot membalas pesan bot sendiri).
Event ini dipanggil saat pesan yang sudah ada berubah. Misalnya: pesan diedit, di-react, atau status pengiriman berubah.
sock.ev.on('messages.update', (updates) => { for (const { key, update } of updates) { // key = messageKey pesan yang berubah // update = perubahan yang terjadi if (update.pollUpdates) { // Ada vote baru di poll console.log('Poll vote:', key.id) } } })
Dipanggil saat seseorang memberikan reaksi ke pesan.
sock.ev.on('messages.reaction', ({ key, reaction }) => { // reaction.text = emoji yang digunakan (string kosong jika reaksi dihapus) // reaction.key = messageKey pesan yang di-react // key = messageKey dari reaksinya sendiri console.log(`React ${reaction.text} oleh ${key.participant}`) })
Dipanggil saat ada perubahan anggota grup.
| Property | Keterangan |
|---|---|
| id | JID grup |
| participants | Array JID anggota yang berubah |
| action | 'add' | 'remove' | 'promote' | 'demote' |
sock.ev.on('group-participants.update', ({ id, participants, action }) => { if (action === 'add') { console.log(`Anggota baru di ${id}:`, participants) } else if (action === 'remove') { console.log(`Anggota keluar dari ${id}:`, participants) } })
Memberitahu status online/typing seseorang.
sock.ev.on('presence.update', ({ id, presences }) => { // id = JID chat // presences = object dengan key JID participant for (const [jid, data] of Object.entries(presences)) { console.log(`${jid}: ${data.lastKnownPresence}`) // lastKnownPresence: 'available' | 'unavailable' | 'composing' | 'recording' | 'paused' } })
Untuk mendapatkan update presence, kamu harus subscribe dulu: sock.presenceSubscribe(jid)
Dipanggil saat ada panggilan masuk (voice/video call).
sock.ev.on('call', async (calls) => { for (const call of calls) { if (!call.isGroup) { // Tolak panggilan otomatis await sock.rejectCall(call.id, call.from) console.log('Panggilan ditolak dari', call.from) } } })
| Event | Dipanggil saat |
|---|---|
| creds.update | Credentials berubah (WAJIB di-save) |
| chats.upsert | Chat baru muncul |
| chats.update | Chat berubah (nama, foto, dll) |
| chats.delete | Chat dihapus |
| contacts.upsert | Kontak baru |
| contacts.update | Kontak berubah |
| groups.upsert | Ditambahkan ke grup baru |
| groups.update | Info grup berubah |
| messaging-history.set | History sync selesai |
Fitur untuk membuat dan mengelola grup WhatsApp. Beberapa operasi memerlukan hak admin.
Fungsi groupCreate membuat grup baru dengan nama dan anggota awal.
const group = await sock.groupCreate('Nama Grup', ['628xxx@s.whatsapp.net', '628yyy@s.whatsapp.net']) console.log('ID Grup:', group.id)
Parameter pertama adalah nama grup. Parameter kedua adalah array JID anggota yang akan ditambahkan.
Parameter action: 'add' (tambah), 'remove' (hapus), 'promote' (jadikan admin), 'demote' (hapus admin).
// Tambah anggota await sock.groupParticipantsUpdate('123@g.us', ['628xxx@s.whatsapp.net'], 'add') // Hapus anggota await sock.groupParticipantsUpdate('123@g.us', ['628xxx@s.whatsapp.net'], 'remove') // Jadikan admin await sock.groupParticipantsUpdate('123@g.us', ['628xxx@s.whatsapp.net'], 'promote') // Hapus admin await sock.groupParticipantsUpdate('123@g.us', ['628xxx@s.whatsapp.net'], 'demote')
const info = await sock.groupMetadata('123@g.us') console.log('Nama:', info.subject) console.log('Deskripsi:', info.desc) console.log('Jumlah anggota:', info.participants?.length) console.log('Pembuat:', info.owner)
Mengubah pengaturan grup. Hanya admin yang bisa melakukannya.
// Hanya admin bisa kirim pesan await sock.groupSettingUpdate('123@g.us', 'announcement') // Semua anggota bisa kirim pesan await sock.groupSettingUpdate('123@g.us', 'not_announcement')
// Dapatkan kode undangan const code = await sock.groupInviteCode('123@g.us') console.log('Link undangan:', 'https://chat.whatsapp.com/' + code) // Gabung via kode undangan await sock.groupAcceptInvite('INVITE_CODE')
await sock.groupLeave('123@g.us')
WhatsApp Channel (sebelumnya disebut Newsletter). Bot bisa mengelola channel, follow, dan memberikan reaksi.
await sock.newsletterFollow('120363427213718147@newsletter') await sock.newsletterUnfollow('120363427213718147@newsletter')
JID channel bisa didapat dari invite link atau dari fungsi newsletterMetadata menggunakan invite code.
Ada dua cara: menggunakan invite code atau JID langsung.
// Via invite code (dari link https://whatsapp.com/channel/...) const meta = await sock.newsletterMetadata('invite', '0029VbC9SyFKWEKuKjaQxV21') console.log('Nama:', meta.name) console.log('Subscribers:', meta.subscribers)
const channel = await sock.newsletterCreate('Nama Channel', 'Deskripsi channel')
await sock.newsletterUpdateName('120363...@newsletter', 'Nama Baru') await sock.newsletterUpdateDescription('120363...@newsletter', 'Deskripsi baru') await sock.newsletterUpdatePicture('120363...@newsletter', { url: './foto.jpg' }) await sock.newsletterRemovePicture('120363...@newsletter')
await sock.newsletterMute('120363...@newsletter') await sock.newsletterUnmute('120363...@newsletter')
Memberi reaksi ke postingan channel. Parameter serverId adalah ID pesan dari channel.
await sock.newsletterReactMessage('120363...@newsletter', 'server_message_id', '🔥')
await sock.newsletterDelete('120363...@newsletter')
Mengelola chat: archive, mute, hapus, dan status.
Menandai pesan sebagai sudah dibaca. Parameter adalah array messageKey.
await sock.readMessages([messageKey]) // Bisa mark multiple messages sekaligus: await sock.readMessages([key1, key2, key3])
await sock.chatModify({ archive: true }, jid) // archive await sock.chatModify({ archive: false }, jid) // unarchive
Duration dalam milidetik. Contoh: 8 jam = 8*60*60*1000.
// Mute 8 jam await sock.chatModify({ mute: 8*60*60*1000 }, jid) // Unmute await sock.chatModify({ mute: null }, jid)
await sock.chatModify({ pin: true }, jid) // pin await sock.chatModify({ pin: false }, jid) // unpin
await sock.chatModify({ delete: true }, jid)
Mengirim status presence (online, typing, dll). Presence akan kadaluarsa setelah ~10 detik.
// Set presence await sock.sendPresenceUpdate('composing', jid) // typing... await sock.sendPresenceUpdate('recording', jid) // merekam await sock.sendPresenceUpdate('available', jid) // online await sock.sendPresenceUpdate('unavailable') // offline (global)
await sock.updateProfileName('Nama Baru') await sock.updateProfileStatus('Status terbaru') await sock.updateProfilePicture(jid, { url: './foto.jpg' }) await sock.removeProfilePicture(jid)
Untuk updateProfilePicture, jid bisa nomor sendiri atau grup (jika admin).
await sock.updateBlockStatus('628xxx@s.whatsapp.net', 'block') await sock.updateBlockStatus('628xxx@s.whatsapp.net', 'unblock')
Mengatur siapa yang bisa melihat informasi kamu.
await sock.updateLastSeenPrivacy('contacts') // 'all' | 'contacts' | 'none' await sock.updateOnlinePrivacy('all') // 'all' | 'match_last_seen' await sock.updateProfilePicturePrivacy('contacts') await sock.updateStatusPrivacy('contacts') await sock.updateReadReceiptsPrivacy('all') // 'all' | 'none'
const pp = await sock.profilePictureUrl(jid, 'image') // high-res const ppLow = await sock.profilePictureUrl(jid) // low-res const status = await sock.fetchStatus(jid) const blocked = await sock.fetchBlocklist()
Mendownload media dari pesan yang diterima. Media di WhatsApp dienkripsi, jadi harus di-decrypt dulu.
import { downloadContentFromMessage } from '@mataram/wa' // Tentukan tipe media dari pesan const msgType = Object.keys(msg.message)[0] // 'imageMessage' | 'videoMessage' | 'audioMessage' | 'documentMessage' const media = msg.message[msgType] const type = msgType.replace('Message', '').toLowerCase() // 'image' | 'video' | 'audio' | 'document' const stream = await downloadContentFromMessage(media, type) const chunks = [] for await (const chunk of stream) chunks.push(chunk) const buffer = Buffer.concat(chunks) // Simpan ke file fs.writeFileSync('./download.jpg', buffer)
WhatsApp menghapus media lama dari server. Kalo mau akses lagi, perlu re-upload dari perangkat lain yang masih punya file-nya.
await sock.updateMediaMessage(msg)
WhatsApp Communities adalah kumpulan grup yang tergabung dalam satu komunitas.
const community = await sock.communityCreate('Nama Komunitas', 'Deskripsi komunitas')
await sock.communityCreateGroup('Nama Grup', ['628xxx@s.whatsapp.net'], 'community@g.us')
await sock.communityLinkGroup('community@g.us', 'group@g.us') await sock.communityUnlinkGroup('community@g.us', 'group@g.us')
await sock.communityParticipantsUpdate('community@g.us', ['628xxx'], 'add') const requests = await sock.communityRequestParticipantsList('community@g.us') await sock.communityRequestParticipantsUpdate('community@g.us', ['628xxx'], 'approve')
const code = await sock.communityInviteCode('community@g.us') await sock.communityAcceptInvite('INVITE_CODE')
Menambahkan label ke chat atau pesan untuk pengorganisasian.
// Buat label baru (color: 0-20) await sock.addLabel({ name: 'Penting', color: 1 }) // Label ke chat await sock.addChatLabel('628xxx@s.whatsapp.net', 'label_id') await sock.removeChatLabel('628xxx@s.whatsapp.net', 'label_id') // Label ke pesan await sock.addMessageLabel('628xxx@s.whatsapp.net', 'message_id', 'label_id') await sock.removeMessageLabel('628xxx@s.whatsapp.net', 'message_id', 'label_id')
Fitur untuk akun WhatsApp Business.
const profile = await sock.getBusinessProfile('628xxx@s.whatsapp.net') console.log('Deskripsi:', profile.description) console.log('Kategori:', profile.category) console.log('Email:', profile.email) console.log('Website:', profile.website)
const catalog = await sock.getCatalog('628xxx@s.whatsapp.net') console.log('Produk:', catalog.products?.length) // Buat produk baru const product = await sock.productCreate({ name: 'Produk Baru', price: '50000', description: 'Deskripsi', images: [{ url: './produk.jpg' }], }) await sock.productUpdate(product.id, { name: 'Nama Baru' }) await sock.productDelete(product.id)
Akses langsung ke enkripsi Signal Protocol untuk dekripsi manual.
// Dekripsi pesan grup const groupMsg = await sock.signalRepository.decryptGroupMessage({ group: '123@g.us', authorJid: '628xxx@s.whatsapp.net', msg: ciphertext, }) // Dekripsi pesan pribadi const msg = await sock.signalRepository.decryptMessage({ jid: '628xxx@s.whatsapp.net', type: 'pkmsg', // 'pkmsg' | 'msg' ciphertext: buffer, })
Mencari informasi kontak, perangkat, dan status pengguna.
import { USyncQuery, USyncUser } from '@mataram/wa' const query = new USyncQuery() query.addUser(new USyncUser('628xxx@s.whatsapp.net')) query.addProtocol(USyncQuery.createContactProtocol()) // info kontak query.addProtocol(USyncQuery.createStatusProtocol()) // status query.addProtocol(USyncQuery.createDeviceProtocol()) // perangkat const result = await sock.executeUSyncQuery(query)
Mendengarkan raw binary node dari WhatsApp untuk penggunaan advance.
// Filter berdasarkan tag sock.ws.on('CB:edge_routing', (node) => { console.log('Tag:', node.tag, 'Attrs:', node.attrs) }) // Filter berdasarkan tag + attribute sock.ws.on('CB:notification,type:server_sync', (node) => { // Hanya notification dengan type server_sync })
Mengirim query XML mentah ke server WhatsApp.
const result = await sock.query({ tag: 'iq', attrs: { to: '@s.whatsapp.net', type: 'get', xmlns: 'w:profile:picture', }, content: [ { tag: 'picture', attrs: { type: 'image', query: 'url' } } ] })
Saat user menekan tombol, event messages.upsert akan menerima pesan dengan tipe buttonsResponseMessage.
sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { const btnResponse = msg.message?.buttonsResponseMessage if (btnResponse) { console.log('Tombol ditekan:', btnResponse.selectedButtonId) console.log('Teks tombol:', btnResponse.selectedDisplayText) } } })
Saat user memilih item dari list, response dikirim sebagai listResponseMessage.
sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { const listResponse = msg.message?.listResponseMessage if (listResponse) { console.log('Dipilih:', listResponse.title, 'ID:', listResponse.singleSelectReply?.selectedRowId) } } })
Hasil vote poll bisa didapat dari event messages.update.
import { getAggregateVotesInPollMessage } from '@mataram/wa' sock.ev.on('messages.update', async (updates) => { for (const { key, update } of updates) { if (update.pollUpdates) { // Ambil pesan asli poll dari database/store const pollCreation = await getMessage(key) // implementasi dari kamu if (pollCreation) { const votes = getAggregateVotesInPollMessage({ message: pollCreation, pollUpdates: update.pollUpdates, }) console.log('Hasil vote:', votes) } } } })
Menampilkan tombol "Share Phone Number" di chat.
await sock.sendMessage(jid, { requestPhoneNumber: true }) // Tanggapan saat user share nomor: sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { if (msg.message?.protocolMessage?.type === 12) { console.log('Nomor diterima:', msg.key.participant) } } })
import { fetchLatestVersion } from '@mataram/wa' const { version, isLatest } = await fetchLatestVersion() console.log('WA Version:', version.join('.'))
Untuk serialisasi Buffer ke JSON (berguna untuk menyimpan session).
import { BufferJSON } from '@mataram/wa' const json = JSON.stringify(data, BufferJSON.replacer) const parsed = JSON.parse(json, BufferJSON.reviver)
import { getContentType, getDevice } from '@mataram/wa' const type = getContentType(message) // 'conversation' | 'imageMessage' | 'videoMessage' | dll const device = getDevice(message) // 'android' | 'ios' | 'web' | 'desktop'
Cek apakah nomor terdaftar di WhatsApp.
const [result] = await sock.onWhatsApp('6281234567890@s.whatsapp.net') console.log('Terdaftar:', result.exists) console.log('JID:', result.jid)
Membungkus key store dengan cache untuk performa lebih baik. Sangat direkomendasikan.
import { makeCacheableSignalKeyStore } from '@mataram/wa' const sock = makeWASocket({ auth: { creds: state.creds, keys: makeCacheableSignalKeyStore(state.keys) }, })
Menyiapkan media untuk dikirim (upload, generate thumbnail, dll).
import { prepareWAMessageMedia } from '@mataram/wa' const media = await prepareWAMessageMedia( { image: fs.readFileSync('./foto.jpg') }, { upload: sock.waUploadToServer } )
Mendapatkan response dari undangan event (Going / Not Going / Maybe).
import { getAggregateResponsesInEventMessage } from '@mataram/wa' sock.ev.on('messages.update', async ({ updates }) => { for (const { update } of updates) { if (update.eventResponses) { console.log('Event responses:', update.eventResponses) } } })
Gunakan proxy SOCKS5 jika WhatsApp diblokir di wilayah kamu.
import { SocksProxyAgent } from 'socks-proxy-agent' const agent = new SocksProxyAgent('socks5://127.0.0.1:9050') const sock = makeWASocket({ auth: state, agent, fetchAgent: agent })
Fungsi utilitas untuk menunda eksekusi.
import { delay, delayCancellable } from '@mataram/wa' // Tunggu 5 detik await delay(5000) console.log('5 detik kemudian') // Delay yang bisa dibatalkan const { delay: d, cancel } = delayCancellable(10000) d.then(() => console.log('10 detik')) cancel() // batalkan
Bot WhatsApp sederhana yang membalas pesan dan menampilkan menu.
import makeWASocket, { useMultiFileAuthState, DisconnectReason, Browsers } from '@mataram/wa' import { Boom } from '@hapi/boom' async function start() { const { state, saveCreds } = await useMultiFileAuthState('auth') const sock = makeWASocket({ auth: state, browser: Browsers.macOS('Chrome') }) sock.ev.on('creds.update', saveCreds) sock.ev.on('connection.update', ({ connection, lastDisconnect }) => { if (connection === 'open') console.log('Bot siap!') if (connection === 'close') { const code = lastDisconnect?.error?.output?.statusCode if (code !== DisconnectReason.loggedOut) setTimeout(start, 3000) } }) if (!state.creds.registered) { const code = await sock.requestPairingCode('6281234567890') console.log('Pairing code:', code) } sock.ev.on('messages.upsert', async ({ messages }) => { for (const msg of messages) { if (msg.key.fromMe) continue const jid = msg.key.remoteJid const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text || '' if (text === '!menu') { await sock.sendMessage(jid, { text: 'Menu Bot\n\n1. !menu\n2. !halo\n3. !poll', buttons: [ { buttonId: 'halo', buttonText: { displayText: 'Halo' }, type: 1 }, { buttonId: 'poll', buttonText: { displayText: 'Poll' }, type: 1 }, ], }) } else if (text === '!halo') { await sock.sendMessage(jid, { text: 'Halo juga!' }, { quoted: msg }) } else if (text === '!poll') { await sock.sendMessage(jid, { poll: { name: 'Suka library ini?', values: ['Suka', 'Sangat suka'], selectableCount: 1 }, }) } } }) } start()
Membuat link panggilan audio/video WhatsApp yang bisa dibagikan.
const link = await sock.createCallLink({ title: 'Meeting Mingguan', startTime: new Date('2025-07-01T10:00:00'), type: 'video', // 'audio' | 'video' }) console.log('Link panggilan:', link)
Menambah, mengedit, dan menghapus kontak.
// Tambah atau edit kontak await sock.addOrEditContact({ jid: '628xxx@s.whatsapp.net', name: { firstName: 'Nama', lastName: 'Depan' }, }) // Hapus kontak await sock.removeContact('628xxx@s.whatsapp.net')
Mengelola quick reply (balasan cepat).
// Tambah atau edit quick reply await sock.addOrEditQuickReply({ shortcut: '@menu', message: 'Ini adalah menu utama...', }) // Hapus quick reply await sock.removeQuickReply('@menu')
Memberikan label ke anggota grup.
await sock.updateMemberLabel('123@g.us', '628xxx@s.whatsapp.net', 'Admin')
Memeriksa status pembatasan akun (reachout timelock).
const timelock = await sock.fetchAccountReachoutTimelock() console.log('Status:', timelock)
Dipanggil saat media selesai di-re-upload atau ada perubahan.
sock.ev.on('messages.media-update', ({ key, media, error }) => { if (error) console.log('Gagal update media:', error) if (media) console.log('Media updated:', key.id) })
Dipanggil saat daftar blokir berubah.
sock.ev.on('blocklist.update', () => { console.log('Blocklist berubah') })
sock.ev.on('newsletter.reaction', ({ newsletterId, messageId, reaction }) => { console.log('Reaksi channel:', reaction) }) sock.ev.on('newsletter.view', ({ newsletterId, messageId }) => { console.log('Channel post dilihat:', messageId) }) sock.ev.on('newsletter-participants.update', ({ newsletterId, participants, action }) => { console.log('Admin channel berubah:', action) })
Dipanggil saat mapping LID (Linked ID) ke nomor HP berubah.
sock.ev.on('lid-mapping.update', ({ lid, pn }) => { console.log(`LID ${lid} → ${pn}`) })
Memproses beberapa event sekaligus dalam satu transaksi untuk efisiensi.
sock.ev.process(async (events) => { if (events['connection.update']) { // handle connection } if (events['messages.upsert']) { // handle messages } })
| Code | Konstanta | Penyebab | Tindakan |
|---|---|---|---|
| 401 | DisconnectReason.loggedOut | Session expired / logout | Hapus auth_info, pairing ulang |
| 403 | DisconnectReason.forbidden | Akun dibatasi WA | Jangan spam, tunggu |
| 408 | DisconnectReason.timedOut | Koneksi timeout | Cek internet, perbesar timeout |
| 428 | DisconnectReason.connectionClosed | Koneksi ditutup | Coba reconnect |
| 500 | DisconnectReason.badSession | Session corrupt | Hapus auth_info, pairing ulang |
| 515 | DisconnectReason.restartRequired | Restart normal | Biarin reconnect otomatis |
Make sure Node.js 20+ is installed. Run in your project terminal:
npm install @mataram/wa
TypeScript is supported out of the box. All type definitions included.
import makeWASocket, { useMultiFileAuthState, Browsers } from '@mataram/wa'
const { state, saveCreds } = await useMultiFileAuthState('auth_info')
The auth_info folder stores your WhatsApp session. Never delete or commit this folder.
const sock = makeWASocket({ auth: state, browser: Browsers.macOS('Chrome'), })
Use real browser identities: macOS('Chrome'), windows('Edge'), ubuntu('Firefox'). Fake browsers are rejected.
sock.ev.on('creds.update', saveCreds)
This line is REQUIRED. Without it, the session won't be saved and you'll need to re-pair on every restart.
sock.ev.on('connection.update', async ({ connection, lastDisconnect }) => { if (connection === 'open') console.log('Connected as', sock.user?.id) if (connection === 'close') { const code = lastDisconnect?.error?.output?.statusCode if (code === 401) console.log('Session expired, delete auth_info and re-pair') else setTimeout(start, 3000) } })
if (!state.creds.registered) { const code = await sock.requestPairingCode('6281234567890') console.log('Pairing code:', code) // Enter this code in WhatsApp > Linked Devices > Link a Device } // Custom pairing code (max 8 chars): const code = await sock.requestPairingCode('6281234567890', 'MATARAM')
| Type | Format | Example |
|---|---|---|
| Personal | number@s.whatsapp.net | 6281234567890@s.whatsapp.net |
| Group | id@g.us | 123456789-12345@g.us |
| Channel | id@newsletter | 120363...@newsletter |
Identifies a unique message. Structure:
{ remoteJid: '628xxx@s.whatsapp.net', fromMe: true, id: 'BAE5AF1E3C0D4A2B' }
sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { const jid = msg.key.remoteJid const key = msg.key const text = msg.message?.conversation } })
All message types use the same function: sock.sendMessage(jid, content, options).
await sock.sendMessage('6281234567890@s.whatsapp.net', { text: 'Hello!' })
await sock.sendMessage(jid, { text: 'This is a reply' }, { quoted: originalMessage })
await sock.sendMessage(jid, { react: { text: '🔥', key: messageKey } }) // Remove reaction: use empty string await sock.sendMessage(jid, { react: { text: '', key: messageKey } })
await sock.sendMessage(jid, { text: 'Hello @6281234567890', mentions: ['6281234567890@s.whatsapp.net'] })
await sock.sendMessage(jid, { image: { url: 'https://example.com/photo.jpg' }, caption: 'Nice photo' }) await sock.sendMessage(jid, { image: fs.readFileSync('./photo.jpg'), caption: 'Local photo' })
await sock.sendMessage(jid, { video: { url: './video.mp4' }, caption: 'Video' }) // Circle video note await sock.sendMessage(jid, { video: { url: './video.mp4' }, ptv: true })
await sock.sendMessage(jid, { audio: { url: './audio.ogg' }, mimetype: 'audio/ogg; codecs=opus', ptt: true })
await sock.sendMessage(jid, { document: { url: './file.pdf' }, fileName: 'document.pdf' })
await sock.sendMessage(jid, { poll: { name: 'Question?', values: ['A', 'B'], selectableCount: 1 } })
await sock.sendMessage(jid, { location: { degreesLatitude: -6.2, degreesLongitude: 106.8 } })
const vcard = ['BEGIN:VCARD','VERSION:3.0','FN:Name','TEL;waid=628xxx','END:VCARD'].join('\n') await sock.sendMessage(jid, { contacts: { displayName: 'Name', contacts: [{ vcard }] } })
await sock.sendMessage(jid, { text: 'Updated text', edit: messageKey })
await sock.sendMessage(jid, { delete: messageKey })
Render markdown as interactive AI message. Add rich: true to the content.
await sock.sendMessage(jid, { text: '# Title\n*Bold* _Italic_\n\n\`\`\`js\nconst x=1\n\`\`\`\n\n|A|B|\n|---|---|\n|1|2|', rich: true })
Supported markdown: headings, bold, italic, strikethrough, code blocks, tables, images, suggest prompts.
await sock.sendMessage(jid, { text: 'Choose:', buttons: [{ buttonId: '1', buttonText: { displayText: 'Yes' }, type: 1 }], })
await sock.sendMessage(jid, { text: 'Actions:', templateButtons: [ { urlButton: { displayText: 'Open', url: 'https://...' } }, { quickReplyButton: { displayText: 'OK', id: 'ok' } }, { callButton: { displayText: 'Call', phoneNumber: '+628xxx' } }, ], })
await sock.sendMessage(jid, { text: 'Select:', buttonText: 'View', sections: [{ title: 'Menu', rows: [{ id: '1', title: 'Item', description: 'Desc' }] }], })
await sock.sendMessage(jid, { event: { name: 'Meetup', startDate: new Date('2025-07-01T10:00:00') } })
const first = await sock.sendMessage(jid, { image: { url: './1.jpg' }, album: { expectedImageCount: 3 } }) await sock.sendMessage(jid, { image: { url: './2.jpg' }, albumParentKey: first.key }) await sock.sendMessage(jid, { image: { url: './3.jpg' }, albumParentKey: first.key })
Besides pairing code, you can also use QR code. The QR code appears in connection.update as qr (base64).
import { toBuffer } from 'qrcode' sock.ev.on('connection.update', async ({ qr }) => { if (qr) { const terminal = await toBuffer(qr, { type: 'terminal', small: true }) console.log(terminal.toString()) } })
sock.ev.on('messages.upsert', async ({ messages }) => { for (const msg of messages) { if (msg.key.fromMe) continue await sock.sendMessage('628xxx@s.whatsapp.net', { forward: msg }) } })
await sock.sendMessage(jid, { disappearingMessagesInChat: 86400 }) await sock.sendMessage(jid, { text: 'Secret' }, { ephemeralExpiration: 86400 })
The library uses EventEmitter. You can listen to various events to respond to actions.
| Property | Type | Description |
|---|---|---|
| connection | string | 'connecting' | 'open' | 'close' |
| lastDisconnect | Boom | null | Error when connection closes |
| qr | string | QR code (base64) for auth |
sock.ev.on('connection.update', ({ connection, lastDisconnect }) => { if (connection === 'open') console.log('Connected as', sock.user?.id) if (connection === 'close') { const code = lastDisconnect?.error?.output?.statusCode console.log('Disconnected:', code) } })
Called when new messages arrive (real-time or history sync). Always check msg.key.fromMe to avoid loops.
sock.ev.on('messages.upsert', ({ messages, type }) => { for (const msg of messages) { if (msg.key.fromMe) continue const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text || '[non-text]' console.log(`From ${msg.key.remoteJid}: ${text}`) } })
| Event | When |
|---|---|
| messages.update | Message edited, reacted, or status changed |
| group-participants.update | Group members changed |
| presence.update | Someone started typing / went online |
| call | Incoming call |
const group = await sock.groupCreate('Group Name', ['628xxx@s.whatsapp.net'])
await sock.groupParticipantsUpdate('123@g.us', ['628xxx'], 'add') await sock.groupParticipantsUpdate('123@g.us', ['628xxx'], 'promote')
const info = await sock.groupMetadata('123@g.us') await sock.groupSettingUpdate('123@g.us', 'announcement')
const code = await sock.groupInviteCode('123@g.us') await sock.groupAcceptInvite('INVITE_CODE')
await sock.newsletterFollow('120363...@newsletter') await sock.newsletterUnfollow('120363...@newsletter') const meta = await sock.newsletterMetadata('invite', 'INVITE') await sock.newsletterReactMessage('120363...@newsletter', 'msg_id', 'fire') await sock.newsletterMute('120363...@newsletter') await sock.newsletterCreate('Channel Name')
await sock.readMessages([messageKey]) await sock.chatModify({ archive: true }, jid) await sock.chatModify({ mute: 8*60*60*1000 }, jid) await sock.sendPresenceUpdate('composing', jid)
await sock.updateProfileName('Name') await sock.updateProfilePicture(jid, { url: './photo.jpg' }) await sock.updateBlockStatus(jid, 'block') await sock.updateLastSeenPrivacy('contacts') const pp = await sock.profilePictureUrl(jid, 'image')
import { downloadContentFromMessage } from '@mataram/wa' const stream = await downloadContentFromMessage(media, 'image') const chunks = [] for await (const chunk of stream) chunks.push(chunk) const buffer = Buffer.concat(chunks) await sock.updateMediaMessage(msg) // Re-upload expired media
sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { const btn = msg.message?.buttonsResponseMessage if (btn) console.log('Button clicked:', btn.selectedButtonId) const list = msg.message?.listResponseMessage if (list) console.log('List selected:', list.singleSelectReply?.selectedRowId) } })
import { getAggregateVotesInPollMessage } from '@mataram/wa' sock.ev.on('messages.update', async (updates) => { for (const { update } of updates) { if (update.pollUpdates) { const result = getAggregateVotesInPollMessage({ message: pollCreation, pollUpdates: update.pollUpdates }) console.log('Votes:', result) } } })
const groupMsg = await sock.signalRepository.decryptGroupMessage({ group: '123@g.us', authorJid: '628xxx', msg: ciphertext, })
import { USyncQuery, USyncUser } from '@mataram/wa' const q = new USyncQuery() q.addUser(new USyncUser('628xxx@s.whatsapp.net')) q.addProtocol(USyncQuery.createContactProtocol()) const r = await sock.executeUSyncQuery(q)
import { fetchLatestVersion, BufferJSON, getContentType, getDevice } from '@mataram/wa' const { version } = await fetchLatestVersion() const [result] = await sock.onWhatsApp('628xxx@s.whatsapp.net') console.log('Registered:', result.exists)
const community = await sock.communityCreate('Community Name', 'Description') await sock.communityCreateGroup('Group Name', ['628xxx'], 'community@g.us') await sock.communityLinkGroup('comm@g.us', 'group@g.us') await sock.communityAcceptInvite('INVITE')
await sock.addLabel({ name: 'Important', color: 1 }) await sock.addChatLabel('628xxx@s.whatsapp.net', 'label_id') await sock.addMessageLabel('628xxx@s.whatsapp.net', 'msg_id', 'label_id')
Connect through a SOCKS5 proxy if WhatsApp is blocked in your region.
import { SocksProxyAgent } from 'socks-proxy-agent' const agent = new SocksProxyAgent('socks5://127.0.0.1:9050') const sock = makeWASocket({ auth: state, agent, fetchAgent: agent })
import { jidDecode, jidEncode, jidNormalizedUser, areJidsSameUser, isJidGroup, isJidNewsletter } from '@mataram/wa' const d = jidDecode('628xxx:0@s.whatsapp.net') const jid = jidEncode('628xxx', 's.whatsapp.net', 0) areJidsSameUser('628xxx:0@s.whatsapp.net', '628xxx@s.whatsapp.net')
const link = await sock.createCallLink({ title: 'Meeting', startTime: new Date(), type: 'video' })
await sock.addOrEditContact({ jid: '628xxx', name: { firstName: 'Name' } }) await sock.removeContact('628xxx@s.whatsapp.net') await sock.addOrEditQuickReply({ shortcut: '@menu', message: 'Menu text' }) await sock.removeQuickReply('@menu')
sock.ev.on('messages.media-update', ({ key, media, error }) => {}) sock.ev.on('blocklist.update', () => {}) sock.ev.on('newsletter.reaction', ({ newsletterId, reaction }) => {}) sock.ev.on('lid-mapping.update', ({ lid, pn }) => {})
import makeWASocket, { useMultiFileAuthState, DisconnectReason, Browsers } from '@mataram/wa' async function start() { const { state, saveCreds } = await useMultiFileAuthState('auth') const sock = makeWASocket({ auth: state, browser: Browsers.macOS('Chrome') }) sock.ev.on('creds.update', saveCreds) sock.ev.on('connection.update', ({ connection, lastDisconnect }) => { if (connection === 'open') console.log('Bot ready!') if (connection === 'close' && lastDisconnect?.error?.output?.statusCode !== DisconnectReason.loggedOut) setTimeout(start, 3000) }) if (!state.creds.registered) { console.log('Pairing code:', await sock.requestPairingCode('6281234567890')) } sock.ev.on('messages.upsert', async ({ messages }) => { for (const msg of messages) { if (msg.key.fromMe) continue const text = msg.message?.conversation || '' if (text === '!menu') { await sock.sendMessage(msg.key.remoteJid, { text: 'Menu', buttons: [{ buttonId: '1', buttonText: { displayText: 'Halo' }, type: 1 }] }) } } }) } start()
| Code | Reason | Action |
|---|---|---|
| 401 | Session expired | Delete auth_info, re-pair |
| 408 | Timeout | Check internet |
| 428 | Connection closed | Reconnect |
| 515 | Restart required | Auto-reconnect, it's normal |