@mataram/wa

TypeScript library for WhatsApp Web API

1. Instalasi

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.

2. Koneksi ke WhatsApp

Untuk terhubung ke WhatsApp, kamu perlu mengikuti langkah-langkah berikut secara berurutan.

2.1. Import Library

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.

2.2. Siapkan Penyimpanan Session

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.

2.3. Buat Koneksi

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:

2.4. Simpan Credentials Saat Berubah

Setiap 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.

2.5. Tangani Perubahan Koneksi

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:

2.6. Pairing Code

Pairing 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
}

Pairing Code Kustom

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.

3. Konsep Dasar

3.1. JID (WhatsApp ID)

JID adalah alamat unik setiap entitas di WhatsApp. Formatnya: nama@domain.

TipeFormatContoh
Personalnomor@s.whatsapp.net6281234567890@s.whatsapp.net
Grupid@g.us123456789-12345@g.us
Channelid@newsletter120363...@newsletter

Untuk nomor pribadi: gunakan kode negara tanpa +. Contoh: 628xxx (Indonesia). Jangan gunakan 08xxx.

3.2. Message Key

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.

3.3. Cara Mendapatkan JID dan MessageKey

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
  }
})

4. Kirim Pesan

Semua jenis pesan dikirim melalui satu fungsi: sock.sendMessage(jid, content, options).

ParameterWajibJenisKeterangan
jidYastringTujuan pesan (personal, grup, atau channel)
contentYaobjectIsi pesan (text, image, video, dll)
optionsTidakobjectOpsi tambahan (quoted, ephemeral, dll)

4.1. Text

Mengirim pesan teks biasa. Parameter text adalah string yang akan dikirim.

await sock.sendMessage('6281234567890@s.whatsapp.net', { text: 'Halo! Ini pesan dari bot.' })

4.2. Reply / Quote

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.

4.3. Reaction

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).

4.4. Mention

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.

4.5. Gambar

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' })

4.6. Video

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 })

4.7. Audio

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 })

4.8. Document

await sock.sendMessage(jid, { document: { url: './file.pdf' }, fileName: 'dokumen.pdf', mimetype: 'application/pdf' })

4.9. Sticker

Sticker harus dalam format WEBP.

await sock.sendMessage(jid, { sticker: { url: './sticker.webp' } })

4.10. Poll

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
  }
})

4.11. Lokasi

await sock.sendMessage(jid, {
  location: {
    degreesLatitude: -6.2088,   // Latitude (contoh: Jakarta)
    degreesLongitude: 106.8456   // Longitude
  }
})

4.12. Kontak

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 }] }
})

4.13. JID Utilities

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')

4.14. View Once

Pesan yang hanya bisa dilihat sekali. Tambahkan viewOnce: true ke content.

await sock.sendMessage(jid, { image: { url: './foto.jpg' }, viewOnce: true })

4.14. Edit Pesan

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.

4.15. Hapus Pesan

Menghapus pesan untuk semua orang. Parameter delete berisi messageKey.

await sock.sendMessage(jid, { delete: messageKey })

5. Rich Response (Markdown Interaktif)

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![gambar](https://picsum.photos/400/300)\n\n:::suggest\nPilih 1 | Pilih 2\n:::',
  rich: true,
})

Fitur markdown yang didukung:

6. Tombol Interaktif

6.1. Native Buttons

Tombol 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.

6.2. Template Buttons

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' } },
  ],
})

6.3. List Select

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' },
    ],
  }],
})

7. Event (Kalender)

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' },
  }
})

8. Album (Multi Gambar)

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.

9. QR Code (Alternatif Auth)

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.

10. Forward Pesan

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 })
  }
})

11. Disappearing Messages

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 })

12. Events (Listener)

Library menggunakan EventEmitter. Kamu bisa mendengarkan berbagai event untuk merespon aksi yang terjadi.

9.1. connection.update

Event ini memberikan informasi tentang status koneksi WebSocket ke WhatsApp.

PropertyJenisKeterangan
connectionstring'connecting' | 'open' | 'close'
lastDisconnectBoom | nullError saat koneksi terputus
qrstring | undefinedQR code (base64) untuk autentikasi
isNewLoginbooleanApakah 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
  }
})

9.2. messages.upsert

Event ini dipanggil saat ada pesan baru masuk (real-time) atau saat sync history.

PropertyJenisKeterangan
messagesWAMessage[]Array pesan yang diterima
typestring'notify' (real-time) | 'append' (history) | 'prepend'
requestIdstring | undefinedID 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).

9.3. messages.update

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)
    }
  }
})

9.4. messages.reaction

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}`)
})

9.5. group-participants.update

Dipanggil saat ada perubahan anggota grup.

PropertyKeterangan
idJID grup
participantsArray 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)
  }
})

9.6. presence.update

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)

9.7. call

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)
    }
  }
})

9.8. Event Lainnya

EventDipanggil saat
creds.updateCredentials berubah (WAJIB di-save)
chats.upsertChat baru muncul
chats.updateChat berubah (nama, foto, dll)
chats.deleteChat dihapus
contacts.upsertKontak baru
contacts.updateKontak berubah
groups.upsertDitambahkan ke grup baru
groups.updateInfo grup berubah
messaging-history.setHistory sync selesai

10. Grup

Fitur untuk membuat dan mengelola grup WhatsApp. Beberapa operasi memerlukan hak admin.

10.1. Membuat Grup

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.

10.2. Menambah/Menghapus Anggota

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')

10.3. Melihat Info Grup

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)

10.4. Pengaturan Grup

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')

10.5. Undangan Grup

// 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')

10.6. Keluar dari Grup

await sock.groupLeave('123@g.us')

11. Newsletter / Channel

WhatsApp Channel (sebelumnya disebut Newsletter). Bot bisa mengelola channel, follow, dan memberikan reaksi.

11.1. Follow / Unfollow Channel

await sock.newsletterFollow('120363427213718147@newsletter')
await sock.newsletterUnfollow('120363427213718147@newsletter')

JID channel bisa didapat dari invite link atau dari fungsi newsletterMetadata menggunakan invite code.

11.2. Melihat Metadata Channel

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)

11.3. Membuat Channel

const channel = await sock.newsletterCreate('Nama Channel', 'Deskripsi channel')

11.4. Update 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')

11.5. Mute / Unmute

await sock.newsletterMute('120363...@newsletter')
await sock.newsletterUnmute('120363...@newsletter')

11.6. React ke Post

Memberi reaksi ke postingan channel. Parameter serverId adalah ID pesan dari channel.

await sock.newsletterReactMessage('120363...@newsletter', 'server_message_id', '🔥')

11.7. Hapus Channel

await sock.newsletterDelete('120363...@newsletter')

12. Chat Management

Mengelola chat: archive, mute, hapus, dan status.

12.1. Mark as Read

Menandai pesan sebagai sudah dibaca. Parameter adalah array messageKey.

await sock.readMessages([messageKey])
// Bisa mark multiple messages sekaligus:
await sock.readMessages([key1, key2, key3])

12.2. Archive Chat

await sock.chatModify({ archive: true }, jid)  // archive
await sock.chatModify({ archive: false }, jid) // unarchive

12.3. Mute Chat

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)

12.4. Pin Chat

await sock.chatModify({ pin: true }, jid)   // pin
await sock.chatModify({ pin: false }, jid)  // unpin

12.5. Delete Chat

await sock.chatModify({ delete: true }, jid)

12.6. Presence / Status

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)

13. Profile & Privacy

13.1. Update Profile

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).

13.2. Block / Unblock

await sock.updateBlockStatus('628xxx@s.whatsapp.net', 'block')
await sock.updateBlockStatus('628xxx@s.whatsapp.net', 'unblock')

13.3. Privacy Settings

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'

13.4. Fetch Profile

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()

14. Media Download

Mendownload media dari pesan yang diterima. Media di WhatsApp dienkripsi, jadi harus di-decrypt dulu.

14.1. Download ke Buffer

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)

14.2. Re-upload Media Kedaluwarsa

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)

15. Communities

WhatsApp Communities adalah kumpulan grup yang tergabung dalam satu komunitas.

15.1. Membuat Community

const community = await sock.communityCreate('Nama Komunitas', 'Deskripsi komunitas')

15.2. Membuat Grup di Dalam Community

await sock.communityCreateGroup('Nama Grup', ['628xxx@s.whatsapp.net'], 'community@g.us')

15.3. Link / Unlink Grup

await sock.communityLinkGroup('community@g.us', 'group@g.us')
await sock.communityUnlinkGroup('community@g.us', 'group@g.us')

15.4. Anggota Community

await sock.communityParticipantsUpdate('community@g.us', ['628xxx'], 'add')
const requests = await sock.communityRequestParticipantsList('community@g.us')
await sock.communityRequestParticipantsUpdate('community@g.us', ['628xxx'], 'approve')

15.5. Invite Community

const code = await sock.communityInviteCode('community@g.us')
await sock.communityAcceptInvite('INVITE_CODE')

16. Labels

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')

17. Business Profile

Fitur untuk akun WhatsApp Business.

15.1. Get Business Profile

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)

15.2. Product Catalog

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)

16. Signal Repository (Enkripsi)

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,
})

17. USync Query

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)

18. Raw WebSocket Events

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
})

19. Custom IQ Query

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' } }
  ]
})

20. Menangani Tombol & Poll

20.1. Button Click

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)
    }
  }
})

20.2. List Select

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)
    }
  }
})

20.3. Poll Vote

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)
      }
    }
  }
})

21. Request Phone Number

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)
    }
  }
})

22. Utilities

22.1. Cek Versi WA

import { fetchLatestVersion } from '@mataram/wa'
const { version, isLatest } = await fetchLatestVersion()
console.log('WA Version:', version.join('.'))

22.2. BufferJSON

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)

22.3. getContentType & getDevice

import { getContentType, getDevice } from '@mataram/wa'
const type = getContentType(message)  // 'conversation' | 'imageMessage' | 'videoMessage' | dll
const device = getDevice(message)    // 'android' | 'ios' | 'web' | 'desktop'

22.4. onWhatsApp

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)

22.5. makeCacheableSignalKeyStore

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) },
})

22.6. prepareWAMessageMedia

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 }
)

22.7. Event Response (RSVP)

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)
    }
  }
})

23. Proxy

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 })

24. Delay & Timer

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

25. Contoh Bot Lengkap

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()

26. Call Links

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)

27. Contact Management

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')

28. Quick Reply

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')

29. Member Label

Memberikan label ke anggota grup.

await sock.updateMemberLabel('123@g.us', '628xxx@s.whatsapp.net', 'Admin')

30. Fetch Account Timelock

Memeriksa status pembatasan akun (reachout timelock).

const timelock = await sock.fetchAccountReachoutTimelock()
console.log('Status:', timelock)

31. Events Tambahan

31.1. messages.media-update

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)
})

31.2. blocklist.update

Dipanggil saat daftar blokir berubah.

sock.ev.on('blocklist.update', () => {
  console.log('Blocklist berubah')
})

31.3. Newsletter Events

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)
})

31.4. lid-mapping.update

Dipanggil saat mapping LID (Linked ID) ke nomor HP berubah.

sock.ev.on('lid-mapping.update', ({ lid, pn }) => {
  console.log(`LID ${lid} → ${pn}`)
})

31.5. Batch Processing

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
  }
})

32. Error Codes

CodeKonstantaPenyebabTindakan
401DisconnectReason.loggedOutSession expired / logoutHapus auth_info, pairing ulang
403DisconnectReason.forbiddenAkun dibatasi WAJangan spam, tunggu
408DisconnectReason.timedOutKoneksi timeoutCek internet, perbesar timeout
428DisconnectReason.connectionClosedKoneksi ditutupCoba reconnect
500DisconnectReason.badSessionSession corruptHapus auth_info, pairing ulang
515DisconnectReason.restartRequiredRestart normalBiarin reconnect otomatis

1. Install

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.

2. Connect to WhatsApp

2.1. Import

import makeWASocket, { useMultiFileAuthState, Browsers } from '@mataram/wa'

2.2. Setup Auth Storage

const { state, saveCreds } = await useMultiFileAuthState('auth_info')

The auth_info folder stores your WhatsApp session. Never delete or commit this folder.

2.3. Create Socket

const sock = makeWASocket({
  auth: state,
  browser: Browsers.macOS('Chrome'),
})

Use real browser identities: macOS('Chrome'), windows('Edge'), ubuntu('Firefox'). Fake browsers are rejected.

2.4. Save Credentials

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.

2.5. Handle Connection

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)
  }
})

2.6. Pairing Code

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')

3. Core Concepts

3.1. JID (WhatsApp ID)

TypeFormatExample
Personalnumber@s.whatsapp.net6281234567890@s.whatsapp.net
Groupid@g.us123456789-12345@g.us
Channelid@newsletter120363...@newsletter

3.2. Message Key

Identifies a unique message. Structure:

{ remoteJid: '628xxx@s.whatsapp.net', fromMe: true, id: 'BAE5AF1E3C0D4A2B' }

3.3. Getting JID and MessageKey from Incoming Messages

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
  }
})

4. Send Messages

All message types use the same function: sock.sendMessage(jid, content, options).

4.1. Text

await sock.sendMessage('6281234567890@s.whatsapp.net', { text: 'Hello!' })

4.2. Reply / Quote

await sock.sendMessage(jid, { text: 'This is a reply' }, { quoted: originalMessage })

4.3. Reaction

await sock.sendMessage(jid, { react: { text: '🔥', key: messageKey } })
// Remove reaction: use empty string
await sock.sendMessage(jid, { react: { text: '', key: messageKey } })

4.4. Mention

await sock.sendMessage(jid, { text: 'Hello @6281234567890', mentions: ['6281234567890@s.whatsapp.net'] })

4.5. Image

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' })

4.6. Video

await sock.sendMessage(jid, { video: { url: './video.mp4' }, caption: 'Video' })
// Circle video note
await sock.sendMessage(jid, { video: { url: './video.mp4' }, ptv: true })

4.7. Audio

await sock.sendMessage(jid, { audio: { url: './audio.ogg' }, mimetype: 'audio/ogg; codecs=opus', ptt: true })

4.8. Document

await sock.sendMessage(jid, { document: { url: './file.pdf' }, fileName: 'document.pdf' })

4.9. Poll

await sock.sendMessage(jid, {
  poll: { name: 'Question?', values: ['A', 'B'], selectableCount: 1 }
})

4.10. Location

await sock.sendMessage(jid, { location: { degreesLatitude: -6.2, degreesLongitude: 106.8 } })

4.11. Contact

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 }] } })

4.12. Edit

await sock.sendMessage(jid, { text: 'Updated text', edit: messageKey })

4.13. Delete

await sock.sendMessage(jid, { delete: messageKey })

5. Rich Response

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.

6. Buttons

6.1. Native Buttons

await sock.sendMessage(jid, {
  text: 'Choose:',
  buttons: [{ buttonId: '1', buttonText: { displayText: 'Yes' }, type: 1 }],
})

6.2. Template Buttons

await sock.sendMessage(jid, {
  text: 'Actions:',
  templateButtons: [
    { urlButton: { displayText: 'Open', url: 'https://...' } },
    { quickReplyButton: { displayText: 'OK', id: 'ok' } },
    { callButton: { displayText: 'Call', phoneNumber: '+628xxx' } },
  ],
})

6.3. List Select

await sock.sendMessage(jid, {
  text: 'Select:', buttonText: 'View',
  sections: [{ title: 'Menu', rows: [{ id: '1', title: 'Item', description: 'Desc' }] }],
})

7. Event

await sock.sendMessage(jid, {
  event: { name: 'Meetup', startDate: new Date('2025-07-01T10:00:00') }
})

8. Album

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 })

9. QR Code (Alternative Auth)

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())
  }
})

10. Forward Messages

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 })
  }
})

11. Disappearing Messages

await sock.sendMessage(jid, { disappearingMessagesInChat: 86400 })
await sock.sendMessage(jid, { text: 'Secret' }, { ephemeralExpiration: 86400 })

12. Events

The library uses EventEmitter. You can listen to various events to respond to actions.

9.1. connection.update

PropertyTypeDescription
connectionstring'connecting' | 'open' | 'close'
lastDisconnectBoom | nullError when connection closes
qrstringQR 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)
  }
})

9.2. messages.upsert

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}`)
  }
})

9.3. Other Events

EventWhen
messages.updateMessage edited, reacted, or status changed
group-participants.updateGroup members changed
presence.updateSomeone started typing / went online
callIncoming call

10. Groups

10.1. Create

const group = await sock.groupCreate('Group Name', ['628xxx@s.whatsapp.net'])

10.2. Members

await sock.groupParticipantsUpdate('123@g.us', ['628xxx'], 'add')
await sock.groupParticipantsUpdate('123@g.us', ['628xxx'], 'promote')

10.3. Info & Settings

const info = await sock.groupMetadata('123@g.us')
await sock.groupSettingUpdate('123@g.us', 'announcement')

10.4. Invite

const code = await sock.groupInviteCode('123@g.us')
await sock.groupAcceptInvite('INVITE_CODE')

11. Newsletter

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')

12. Chat

await sock.readMessages([messageKey])
await sock.chatModify({ archive: true }, jid)
await sock.chatModify({ mute: 8*60*60*1000 }, jid)
await sock.sendPresenceUpdate('composing', jid)

13. Profile

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')

14. Media Download

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

15. Button & Poll Handling

15.1. Button Click

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)
  }
})

15.2. Poll Votes

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)
    }
  }
})

16. Signal Repository

const groupMsg = await sock.signalRepository.decryptGroupMessage({
  group: '123@g.us', authorJid: '628xxx', msg: ciphertext,
})

17. USync Query

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)

18. Utilities

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)

19. Communities

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')

17. Labels

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')

18. Proxy

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 })

19. JID Utilities

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')

20. Call Links

const link = await sock.createCallLink({ title: 'Meeting', startTime: new Date(), type: 'video' })

21. Contact & Quick Reply

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')

22. Additional Events

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 }) => {})

23. Full Bot Example

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()

24. Error Codes

CodeReasonAction
401Session expiredDelete auth_info, re-pair
408TimeoutCheck internet
428Connection closedReconnect
515Restart requiredAuto-reconnect, it's normal