Complete reference of error codes, their causes, and solutions.
All connection errors are wrapped in Boom from @hapi/boom. Check the status code via error.output.statusCode.
| Code | Constant | Cause | Solution |
|---|---|---|---|
| 401 | DisconnectReason.loggedOut |
Session expired, manual logout, or account logged in on another device. | Delete auth folder and re-pair. Do not auto-reconnect. |
| 403 | DisconnectReason.forbidden |
Account banned or temporarily restricted by WhatsApp. | Check WhatsApp Web. If banned, cannot be recovered. If temp ban, wait. |
| 408 | DisconnectReason.timedOut |
Connection timeout — server did not respond within the expected interval. | Auto-reconnect. Check internet stability. |
| 408 | DisconnectReason.connectionLost |
Keep-alive failed — server did not respond to pings. | Auto-reconnect. Check network. |
| 428 | DisconnectReason.connectionClosed |
Connection closed by server or WebSocket error. | Auto-reconnect. If frequent, check proxy/network stability. |
| 440 | DisconnectReason.connectionReplaced |
Another device connected with the same account. | If intentional, ignore. If not, change WA password. |
| 500 | DisconnectReason.badSession |
Corrupt session — Signal keys or auth data are damaged. | Delete auth folder and re-pair. Session cannot be recovered. |
| 503 | DisconnectReason.unavailableService |
WhatsApp server overloaded / under maintenance. | Reconnect with longer delay (5-10s). Do not spam reconnect. |
| 515 | DisconnectReason.restartRequired |
Server requests connection restart. Normal after first successful pairing. | Auto-reconnect. This is part of the pairing flow. |
| 411 | DisconnectReason.multideviceMismatch |
Account has not joined Multi-Device beta. | Open WhatsApp → Linked Devices → Multi-Device beta → Join. |
Errors sent by the server via <stream:error>. Usually appear before disconnect.
| Code | Message | Cause & Solution |
|---|---|---|
| 401 | not-authorized | Invalid session. Delete auth & re-pair. |
| 403 | forbidden | Account restricted. Check WhatsApp Web. |
| 404 | item-not-found | Resource not found (usually internal). |
| 405 | not-allowed | Action not permitted. |
| 409 | conflict | Session conflict — another device connected. |
| 500 | internal-server-error | WA internal server error. Try reconnecting. |
| Code | Cause | Solution |
|---|---|---|
| SUCCESS (200) | Media retry successful | Nothing to do |
| GENERAL_ERROR | General error during media re-download | Retry updateMediaMessage |
| NOT_FOUND | Media no longer on server (expired) | Ask sender to resend |
| DECRYPTION_ERROR | Decryption key corrupted | Ask sender to resend |
| 400 | Invalid media URL or missing directPath | Verify the message actually contains media |
| Code | Cause | Solution |
|---|---|---|
| 408 | QR timeout — not scanned in time | Request new QR/code |
| 500 | Pairing failed — key mismatch | Retry pairing |
| 400 | Invalid phone number format | Use international format without + |
| Code | Cause | Solution |
|---|---|---|
| 463 | MessageAccountRestriction — account temporarily restricted | Wait a few hours. Don't spam. Check fetchAccountReachoutTimelock |
| 479 | Server rejected message (tctoken / privacy) | Usually auto-handled. Check logs if frequent |
| 408 | Media upload timeout | Retry with larger mediaUploadTimeoutMs |
| 500 | "All encryptions failed" | Check Signal sessions. May need session re-sync |
import { Boom, DisconnectReason, delay } from '@mataram/wa' const handleDisconnect = async (lastDisconnect) => { const error = lastDisconnect?.error const statusCode = error?.output?.statusCode switch (statusCode) { case DisconnectReason.loggedOut: console.error('Session expired. Re-pair required.') return false case DisconnectReason.badSession: console.error('Corrupt session. Delete auth folder.') return false case DisconnectReason.restartRequired: console.log('Restart required, reconnecting...') return true default: console.log('Reconnecting in 3s...') await delay(3000) return true } } sock.ev.on('connection.update', async ({ connection, lastDisconnect }) => { if (connection === 'close') { const shouldReconnect = await handleDisconnect(lastDisconnect) if (shouldReconnect) start() } })
logger.child({ level: 'debug' }) or 'trace' to see raw XML stanzassock.fetchAccountReachoutTimelock() to check account restriction statussock.fetchNewChatMessageCap() to view message quotaev.off() when listeners are no longer needed. Double registration writes creds twicet attribute in server responsesWhatsApp has several mechanisms to restrict accounts it deems spammy. Understanding these is critical so your bot doesn't get silently limited.
Account has a reachout timelock. Unlike a ban, this is temporary (usually 24-72h). WhatsApp will reject messages to new contacts (1:1) because the privacy token (tctoken) is withheld. Existing chats remain safe since they already carry a tctoken.
@mataram/wa automatically detects this error when receiving a NACK from the server and calls fetchAccountReachoutTimelock() to update the status. Do not keep sending while restricted — retrying only makes it worse.
Server rejected the stanza due to a stale device session or incorrect stanza addressing. Usually occurs when the Signal session has expired or there's a mismatch between the registered device and the server. @mataram/wa auto-handles this by recreating the session or re-sending the stanza with the correct session.
| Method | Purpose |
|---|---|
sock.fetchAccountReachoutTimelock() |
Check if the account has a reachout timelock, the enforcement type, and when it ends. Emits a connection.update event with the reachoutTimeLock field. |
sock.fetchNewChatMessageCap() |
Check the new chat quota per cycle. Returns total_quota, used_quota, and capping_status (NONE / FIRST_WARNING / SECOND_WARNING / CAPPED). |
@mataram/wa has an automatic retry system for messages that fail to send. These configs can be set during socket init:
| Config | Default | Purpose |
|---|---|---|
maxMsgRetryCount |
5 |
Maximum retry attempts per message. Beyond this limit, the message won't be retried. |
enableRecentMessageCache |
true |
Cache recent messages in memory so they can be re-sent on retry. Disable if you need to save memory. |
enableAutoSessionRecreation |
true |
Automatically recreate the Signal session if it's corrupt/expired during retry. Prevents error 500 "All encryptions failed". |
When retry count > 1 and enableAutoSessionRecreation is on, @mataram/wa automatically checks the Signal session to the target JID. If the session is corrupt or missing, it will be recreated before the retry attempt. This helps avoid reconnect spam — especially with error 479 (smax-invalid) which usually resolves after a session recreation.