@zapo-js/fake-server is an in-process fake WhatsApp Web server that speaks Noise XX/IK, X3DH + Double Ratchet, group SenderKey, app-state sync, and media upload/download over self-signed HTTPS — everything a real conforming client sees on the wire. It exists so you can test your app end-to-end without touching WhatsApp: no phone tied up, no risk to a production account, no flakiness from a live network, CI-friendly.
This guide targets app authors — bots, CRMs, notification services, integrations — regardless of whether the underlying WhatsApp library is
zapo-js, Baileys, whatsmeow, or another conforming client. For the library-internal use (cross-check suite, benchmarks) see Dev tools.Works with any WhatsApp library
The fake server is a wire-level implementation of the WhatsApp protocol. It has nozapo-js dependency at runtime — it speaks the same Noise / Signal / app-state bytes as WhatsApp’s own edges. Strict clients (Baileys forks, whatsmeow, and others) work against it out of the box:
- The Noise cert chain sets
notBefore/notAfterso validity-checking clients accept it as unexpired. - The passive-set IQ and the encrypt
<count>prekey-count query are answered by default (non-zapoclients block on both). - After every login the server sends
<ib><offline count="0"/></ib>so buffered-event clients flush. FakePeermessage ids are WA-style hex — no@characters that would tokenize as a JID in a strict decoder.
Install
zapo-js is a peer dependency of the fake server (it imports the underlying Noise / Signal / crypto / proto primitives at runtime), so it needs to be present in the tree even when your app is built on a different WhatsApp library. Node.js >= 20.9.0.
Quick start (programmatic)
Thezapo-js wiring below is what a zapo-js app looks like; for Baileys / whatsmeow, keep your existing client setup and only override the socket URL, media proxy, and Noise root CA to point at the fake server.
testHooks.noiseRootCa trusts the fake server’s certificate without bypassing verification — the full cert-chain check still runs.
Simulating a peer
createFakePeer gives you a simulated contact backed by real Signal crypto. Push messages to your client with sendConversation / sendGroupConversation, and capture what your client sends with expectMessage:
Asserting on outbound stanzas
For assertions that don’t need a peer’s viewpoint (a<presence> your app sent, a <receipt> it acked, an IQ it made) subscribe to captured stanzas:
onCapturedStanza returns an unsubscribe function; call it in afterEach to keep tests isolated.
Multi-session isolation
Run many app instances against a single server without cross-talk. Provide asessionKey resolver — the fake server routes each authenticated connection to its own FakeServerSession (isolated peers, groups, prekeys, app-state, captured stanzas):
info.clientPayload is available for keying by login identity. Server-wide handlers (server.registerIqHandler) still apply to every session; session-scoped ones (session.registerIqHandler) stay local.
Programmatic config
FakeWaServer.start(options) accepts:
onPipeline(listener) fans out to multiple subscribers and returns an unsubscribe function; the pipeline’s parsed clientPayload is exposed on WaFakeConnectionPipeline for identity checks. IQ handlers may return null to fall through to the next matching handler (observe-then-delegate).
Standalone CLI
The package ships afake-wa-server binary. Run it once the dev dep is installed:
--pair <jid> mode drives QR pairing by prompting on stdin for the QR payload the client displays — pair once, then reconnect against the same fake server to iterate.
CI recipe
Spin one server per test file (or per suite), tear it down at the end.memory providers on the client keep every test hermetic:
Pair the fake server with in-memory stores on the client. Every test resets on run — no leaked pairing state, no fixture disk juggling.
See also
Dev tools
Benchmarks, cross-check suite, and the MCP dev-server — internal library tooling.
Fake server README
Full CLI reference and API surface.
