Skip to main content
@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 no zapo-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 / notAfter so validity-checking clients accept it as unexpired.
  • The passive-set IQ and the encrypt <count> prekey-count query are answered by default (non-zapo clients block on both).
  • After every login the server sends <ib><offline count="0"/></ib> so buffered-event clients flush.
  • FakePeer message ids are WA-style hex — no @ characters that would tokenize as a JID in a strict decoder.
If your app runs against a real WA edge, it should run against the fake one with only a URL / cert swap.

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)

The zapo-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:
The peer performs a real X3DH handshake, ratchets each message forward, and validates MACs — so a bug in your app’s Signal handling surfaces the same way it would against WhatsApp itself.

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 a sessionKey resolver — the fake server routes each authenticated connection to its own FakeServerSession (isolated peers, groups, prekeys, app-state, captured stanzas):
The resolver runs once per connection, right after authentication, so 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 a fake-wa-server binary. Run it once the dev dep is installed:
The --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.
Last modified on July 24, 2026