Skip to main content
You’re almost certainly running on the in-memory store, which loses credentials when the process exits. Use a durable backend (SQLite, Postgres, …) for the auth domain, and keep a stable sessionId across runs — changing it orphans the previous credentials. See Stores.
By design — zapo does not auto-reconnect. Listen for the connection event with status: 'close' and call connect() again (skip it when isLogout is true). See the reconnection pattern.
Media still uploads without @zapo-js/media-utils — but without it there’s no processor to generate thumbnails/previews, image-video dimensions, or voice-note waveforms, so it can render as a plain attachment or with no preview. For proper media, install it (npm i @zapo-js/media-utils, plus ffmpeg/ffprobe) and wire a processor through the media option. See Media.
Pass a file path (string) or a Readable stream to media, not a Buffer — zapo streams the bytes through the pipeline so memory stays flat for large files. On download, prefer downloadToFile/download over downloadBytes.
The proxy.ws leg needs the ws package (the runtime’s native WebSocket can’t take an HTTP Agent). Media/link-preview legs use an undici dispatcher. See the proxy examples for SOCKS/HTTP/HTTPS and IPv4/IPv6.
Always reply to event.key.remoteJid (the group JID), never a participant’s JID. When you have a peer’s LID, prefer the LID — it’s the privacy-preserving, forward-compatible identity. See Identities (PN vs LID).
That’s multi-device sync — your own sends come back on the message event flagged key.fromMe === true. Filter them out if you only want inbound traffic. See Receiving messages.
Import the event type from the package root — all coordinator and event types are exported:
No. Mobile connections are stable, but zapo intentionally does not provide a registration API — registering a number is complex and requires a physical phone. You connect with already-registered credentials. See Mobile connections.
Both work. QR is the default (auth_qr event). For an 8-character code, call client.auth.requestPairingCode(phone) after the auth_pairing_required event. See Authentication.
disconnect() closes the socket but keeps credentials so you can resume later. logout() unlinks the device server-side and clears stored state (per logoutStoreClear). See Authentication.
WhatsApp rejected the bundled WA Web version. Upgrade zapo when possible. As a stopgap, set recoverFromClientTooOld: true to auto-fetch the current version and retry, or pass a version resolver that returns a fresh string per connect. See WhatsApp Web version.
Some operations are gated: editBusinessProfile, cover-photo ops, and broadcast lists are business-only; email binding is mobile-only; several community/newsletter ops require an active MEX transport. The coordinator reference flags each.

Still stuck?

Architecture in depth

Understand the layers to debug at the protocol level.

Low-level API

Inspect raw stanzas with the debug events and lowlevel.
Last modified on May 29, 2026