Business tools, communities, labels, Signal, USync, media download, proxy, and utility functions.
Fetch another user's business profile.
const profile = await sock.getBusinessProfile('628xxx@s.whatsapp.net') console.log(profile.description, profile.email, profile.website, profile.address) console.log(profile.category, profile.business_hours)
Update your own business profile.
await sock.updateBussinesProfile({ address: '123 Main St', email: 'store@example.com', description: 'Trusted online store', websites: ['https://example.com'], hours: { timezone: 'America/New_York', days: [ { day: 'mon', mode: 'specific_hours', openTimeInMinutes: 480, closeTimeInMinutes: 1020 }, { day: 'tue', mode: 'open_24h' }, { day: 'sun', mode: 'closed' }, ], }, })
const photoId = await sock.updateCoverPhoto({ url: './cover.jpg' }) await sock.removeCoverPhoto(photoId)
List products from a catalog.
const catalog = await sock.getCatalog({ jid: '628xxx@s.whatsapp.net', limit: 20 }) console.log(catalog.products) console.log(catalog.cursor) // for pagination const nextPage = await sock.getCatalog({ jid, cursor: catalog.cursor })
const collections = await sock.getCollections('628xxx@s.whatsapp.net', 20)
// Create a new product const product = await sock.productCreate({ name: 'Premium T-Shirt', description: '100% cotton T-shirt', images: [{ url: 'https://example.com/tshirt.jpg' }], currency: 'IDR', price: 5000000, // Rp50.000 (price in cents x 100) originCountryCode: 'ID', isHidden: false, }) // Update product await sock.productUpdate(productId, { name: 'Premium T-Shirt V2', price: 7500000, }) // Delete product const result = await sock.productDelete([productId]) console.log('Deleted:', result.deleted) // Order details const order = await sock.getOrderDetails(orderId, tokenBase64)
Community features — large groups with sub-groups.
// Create community const community = await sock.communityCreate('Community Name', 'Description') // Create group inside community const group = await sock.communityCreateGroup('Sub Group', ['628xxx@s.whatsapp.net'], '123456@g.us') // Get community metadata const meta = await sock.communityMetadata('123456@g.us') // Link / unlink group to community await sock.communityLinkGroup('subgroup@g.us', 'community@g.us') await sock.communityUnlinkGroup('subgroup@g.us', 'community@g.us') // Fetch all linked groups const linked = await sock.communityFetchLinkedGroups('community@g.us') console.log(linked.linkedGroups) // Leave community await sock.communityLeave('123456@g.us') // Update name & description await sock.communityUpdateSubject('123456@g.us', 'New Name') await sock.communityUpdateDescription('123456@g.us', 'New description')
// Add / remove members await sock.communityParticipantsUpdate('123@g.us', ['628xxx@s.whatsapp.net'], 'add') await sock.communityParticipantsUpdate('123@g.us', ['628xxx@s.whatsapp.net'], 'remove') await sock.communityParticipantsUpdate('123@g.us', ['628xxx@s.whatsapp.net'], 'promote') // Invite code const inviteCode = await sock.communityInviteCode('123@g.us') await sock.communityRevokeInvite('123@g.us') const jid = await sock.communityAcceptInvite('invitecode') const info = await sock.communityGetInviteInfo('invitecode') // Join requests const requests = await sock.communityRequestParticipantsList('123@g.us') await sock.communityRequestParticipantsUpdate('123@g.us', ['628xxx@s.whatsapp.net'], 'approve')
await sock.communitySettingUpdate('123@g.us', 'announcement') await sock.communityToggleEphemeral('123@g.us', 86400) await sock.communityJoinApprovalMode('123@g.us', 'on') await sock.communityMemberAddMode('123@g.us', 'admin_add') // Fetch all communities const all = await sock.communityFetchAllParticipating()
Labels for chats and messages.
// Add label await sock.addLabel('123@g.us', { name: 'Important', labelColor: 1 }) // Label chat await sock.addChatLabel('628xxx@s.whatsapp.net', 'label_id') await sock.removeChatLabel('628xxx@s.whatsapp.net', 'label_id') // Label specific message await sock.addMessageLabel('628xxx@s.whatsapp.net', 'message_id', 'label_id') await sock.removeMessageLabel('628xxx@s.whatsapp.net', 'message_id', 'label_id')
Direct access to the Signal repository for debugging or manual operations.
Decrypt a 1-on-1 message.
const decrypted = await sock.signalRepository.decryptMessage({ jid: '628xxx@s.whatsapp.net', type: 'pkmsg', // or 'msg' ciphertext: buffer, })
Decrypt a group message (sender key).
const decrypted = await sock.signalRepository.decryptGroupMessage({ group: '123@g.us', data: cipherBuffer, meId: sock.user?.id, })
// Encrypt 1-on-1 const { type, ciphertext } = await sock.signalRepository.encryptMessage({ jid: '628xxx@s.whatsapp.net', data: plaintextBuffer, }) // Encrypt group const result = await sock.signalRepository.encryptGroupMessage({ group: '123@g.us', data: plaintextBuffer, meId: sock.user?.id, })
The library automatically handles session management, pre-key upload, etc. You can listen to the creds.update event to see key changes.
WhatsApp query protocol for fetching user data (devices, status, LID mapping, etc.).
import { USyncQuery, USyncUser } from '@mataram/wa' const query = new USyncQuery() .withContext('business') .withDeviceProtocol() .withContactProtocol() .withLIDProtocol() .withStatusProtocol() .withDisappearingModeProtocol() .withUser(new USyncUser().withId('628xxx@s.whatsapp.net')) .withUser(new USyncUser().withPhone('+6281234567890')) const result = await sock.executeUSyncQuery(query) console.log(result.list) // Each item: { id, device, contact, lid, status, disappearingMode }
| Method | Description |
|---|---|
withId(jid) | Look up user by JID |
withPhone(phone) | Look up user by phone number (format: +628xxx) |
| Protocol | Method | Description |
|---|---|---|
| Device | .withDeviceProtocol() | Get user device list |
| Contact | .withContactProtocol() | Check if number is registered on WA |
| LID | .withLIDProtocol() | Get LID to PN mapping |
| Status | .withStatusProtocol() | Get user status/about |
| Disappearing Mode | .withDisappearingModeProtocol() | Get disappearing mode settings |
Check if phone numbers are registered on WhatsApp. Simpler than manual USync.
const results = await sock.onWhatsApp('6281234567890', '6289876543210') for (const { jid, exists } of results) { console.log(jid, exists ? 'Registered' : 'Not registered') }
Create audio/video call links.
// Create video call link const token = await sock.createCallLink('video') console.log('Call link:', `https://call.whatsapp.com/video/${token}`) // With schedule const token2 = await sock.createCallLink('audio', { startTime: new Date('2025-07-01T10:00:00').getTime() })
// Add / edit contact await sock.addOrEditContact('628xxx@s.whatsapp.net', { firstName: 'John', fullName: 'John Doe', phoneNumber: [{ phone: '+6281234567890' }], }) // Remove contact await sock.removeContact('628xxx@s.whatsapp.net') // Block / unblock user await sock.updateBlockStatus('628xxx@s.whatsapp.net', 'block') await sock.updateBlockStatus('628xxx@s.whatsapp.net', 'unblock') // Fetch blocklist const blocklist = await sock.fetchBlocklist()
// Add / edit quick reply await sock.addOrEditQuickReply({ name: '.help', shortcut: '.help', message: 'Available commands:\n!menu - Menu\n!about - About', }) // Remove quick reply await sock.removeQuickReply(timestamp)
Download media from messages (image, video, audio, document).
Low-level function. Download & decrypt stream directly from WA servers.
import { downloadContentFromMessage, toBuffer } from '@mataram/wa' const downloadMedia = async (msg) => { const content = msg.message?.imageMessage // or videoMessage, audioMessage, documentMessage if (!content) return const stream = await downloadContentFromMessage(content, 'image') const buffer = await toBuffer(stream) // Save to file fs.writeFileSync('./download.jpg', buffer) } // Supported media types // 'image' | 'video' | 'audio' | 'document' | 'sticker'
High-level function. Download directly from a WAMessage object.
import { downloadMediaMessage } from '@mataram/wa' const buffer = await downloadMediaMessage(msg, 'buffer') // get Buffer directly const stream = await downloadMediaMessage(msg, 'stream') // get Readable stream
Use a proxy for WebSocket connection and fetch requests.
import { SocksProxyAgent } from 'socks-proxy-agent' const agent = new SocksProxyAgent('socks5://127.0.0.1:1080') const sock = makeWASocket({ auth: state, agent, // proxy for WebSocket fetchAgent: agent, // proxy for upload/download media })
HTTP/HTTPS proxy can also be used with HttpsProxyAgent from https-proxy-agent.
Get the latest WA Web version.
const [version, isNew] = await fetchLatestVersion() console.log('Version:', version.join('.'))
JSON serializer/deserializer that supports Buffer. Used internally for saving creds.
import { BufferJSON } from '@mataram/wa' const data = { buffer: Buffer.from('hello') } const json = JSON.stringify(data, BufferJSON.replacer) const parsed = JSON.parse(json, BufferJSON.reviver)
Get the content type from a message.
import { getContentType } from '@mataram/wa' const type = getContentType(msg.message) // 'conversation' | 'imageMessage' | 'videoMessage' | 'extendedTextMessage' | 'pollCreationMessage' | ...
Detect which device sent the message.
import { getDevice } from '@mataram/wa' const device = getDevice(msg.key.id) // 'ios' | 'android' | 'web' | 'unknown'
Wrap key store with cache to avoid reading from disk every time.
import { makeCacheableSignalKeyStore } from '@mataram/wa' const cachedKeys = makeCacheableSignalKeyStore(authState.keys)
Simple sleep helper.
import { delay } from '@mataram/wa' await delay(3000) // wait 3 seconds
const statusList = await sock.fetchStatus('628xxx@s.whatsapp.net', '628yyy@s.whatsapp.net') for (const { id, status, setAt } of statusList) { console.log(id, status, setAt) }
const result = await sock.fetchDisappearingDuration('628xxx@s.whatsapp.net') console.log(result)
Subscribe to a user's presence updates.
await sock.presenceSubscribe('628xxx@s.whatsapp.net')
Get the list of available WA bots.
const bots = await sock.getBotListV2() console.log(bots)
Update a group member's label.
await sock.updateMemberLabel('123@g.us', 'member')
Request a re-download for failed media (retry).
try { await sock.updateMediaMessage(msg) console.log('Media updated!') } catch (err) { console.log('Failed to update media', err) }
Explicitly logout from WhatsApp.
await sock.logout()
WhatsApp has anti-spam systems that can restrict bot accounts. These functions let you check your standing.
Checks if your account is temporarily restricted from reaching out to new contacts. Usually triggered by sending messages to too many new numbers in a short time.
| Return | Type | Description |
|---|---|---|
| isActive | boolean | Whether the account is currently restricted |
| timeEnforcementEnds | Date | undefined | When the restriction ends |
| enforcementType | ReachoutTimelockEnforcementType | Type of restriction |
| enforcementType | Meaning |
|---|---|
| DEFAULT | Normal, no restriction |
| WEB_COMPANION_ONLY | Only companion/web devices blocked, main phone OK |
| RESTRICT_ALL_COMPANIONS | All devices blocked from reaching new contacts |
| BIZ_QUALITY | Low business quality score |
| BIZ_COMMERCE_VIOLATION_* | Commerce violations (15 types: ALCOHOL, DRUGS, WEAPONS, etc.) |
const timelock = await sock.fetchAccountReachoutTimelock() if (timelock.isActive) { console.log('Account restricted:', timelock.enforcementType) console.log('Ends:', timelock.timeEnforcementEnds) }
This is different from a ban. Usually temporary (24-72h). During restriction, avoid sending messages to new contacts — retrying only makes it worse. @mataram/wa automatically handles error 463 (SenderReachoutTimelocked) and fetches this status.
WhatsApp also enforces a new chat quota per cycle. Different from reachout timelock: this limits the count, not blocking entirely.
| Field | Type | Description |
|---|---|---|
| capping_status | string | NONE / FIRST_WARNING / SECOND_WARNING / CAPPED |
| total_quota | number | Total new chat quota in this cycle |
| used_quota | number | Quota already used |
| cycle_start_timestamp | string | Unix timestamp cycle started |
| cycle_end_timestamp | string | Unix timestamp cycle ends |
| ote_status | string | NOT_ELIGIBLE / ELIGIBLE / ACTIVE_IN_CURRENT_CYCLE / EXHAUSTED |
| mv_status | string | NOT_ELIGIBLE / NOT_ACTIVE / ACTIVE / ACTIVE_UPGRADE_AVAILABLE |
const cap = await sock.fetchNewChatMessageCap() console.log('Status:', cap.capping_status) console.log('Quota:', cap.used_quota + '/' + cap.total_quota)
Send markdown-formatted messages rendered as native AI bot responses — tables, code blocks, syntax highlighting, and suggested prompts.
Enable rich response by adding rich: true to the content object. @mataram/wa converts the markdown into a native interactive message that WhatsApp renders as an AI bot response.
await sock.sendMessage(jid, { text: '# Dashboard\n\n| Metric | Value |\n|--------|-------|\n| Users | 1.234 |\n\n```js\nconsole.log("ok")\n```\n\n> Bot running 24/7\n\n:::suggest\nCheck Status | Restart | Help\n:::', rich: true })
| Markdown | Result |
|---|---|
# Heading | Large heading |
**bold** | Bold |
*italic* | Italic |
~~strikethrough~~ | Strikethrough |
`code` | Inline code |
```js ... ``` | Code block with syntax highlighting (js, ts, python, go, java, rust) |
| col1 | col2 | | Table |
 | Image |
> quote | Blockquote |
:::suggest ... ::: | Suggested prompt pills (recommendation buttons) |
Rich response uses botForwardedMessage — Meta AI's internal message type. Only works on WhatsApp versions with AI bot support. If WhatsApp shows "Version not supported", your client doesn't have this feature yet.
@mataram/wa has an advanced automatic retry system far beyond original Baileys.
| Feature | Original Baileys | @mataram/wa |
|---|---|---|
| Retry cache | None | LRU cache 512 entries, 5min TTL |
| Retry counter | Basic counter | Per-message, max configurable |
| Auto session recreate | Manual | Automatic (configurable) |
| Base key collision | Not checked | Detect + recreate session |
| MAC error | Not handled | Auto recreate session |
| Error 463 (restricted) | Not handled | Auto issue tctoken + recovery |
| Error 479 (smax-invalid) | Not handled | Logged, skip retry |
| Retry >24h | Still retries | Skip (expired) |
| Phone request | None | Delayed 3s, timeout 8s |
| Option | Default | Description |
|---|---|---|
| maxMsgRetryCount | 5 | Max retry attempts per message |
| enableRecentMessageCache | true | Cache recent messages for resend on retry |
| enableAutoSessionRecreation | true | Auto recreate Signal session if corrupt |
| getMessage | () => undefined | Callback to fetch message from your DB |
| Code | Name | Cause |
|---|---|---|
| 0 | UnknownError | Unknown error |
| 1 | NoSession | No Signal session exists |
| 2 | InvalidKey | Invalid key |
| 3 | InvalidKeyId | Wrong key ID |
| 4 | InvalidMessage | Corrupt message |
| 5 | InvalidSignature | Invalid signature |
| 6 | FutureMessage | Message from the future (counter out of sync) |
| 7 | BadMac | MAC check failed — auto recreate session |
| 8 | InvalidSession | Invalid session — auto recreate |
| 9 | InvalidMsgKey | Wrong message key |
| 10 | BadBroadcastEphemeralSetting | Wrong ephemeral broadcast setting |
| 11 | UnknownCompanionNoPrekey | Companion has no pre-key |
| 12 | AdvFailure | Advanced failure |
| 13 | StatusRevokeDelay | Status revoke delay |