Skip to main content

Documentation Index

Fetch the complete documentation index at: https://zapo.to/llms.txt

Use this file to discover all available pages before exploring further.

zapo is built for long-lived, multi-session workloads. This page collects the operational decisions that matter once you move past a local prototype.

Persist credentials (don’t re-pair)

Production sessions must use a durable store for the auth and Signal domains, plus a stable sessionId across restarts. The in-memory store loses everything on exit, forcing a re-pair on every boot.
const client = new WaClient({ store, sessionId: 'tenant-42' }, logger)
Changing sessionId orphans the previous credentials — treat it as the durable key for a device/account.

Choose a store backend

BackendBest for
@zapo-js/store-sqliteSingle process / single host — the simplest, fastest local option.
@zapo-js/store-postgres · @zapo-js/store-mysqlMultiple hosts, relational ops, managed backups.
@zapo-js/store-redisLow-latency cache + persistence.
@zapo-js/store-mongoDocument-oriented deployments.
You can mix backends per domain (e.g. auth/signal in Postgres, caches in Redis). See Installation and the stores reference.

Graceful shutdown

Call disconnect() (never logout()) on shutdown — it flushes pending write-behind data and closes the socket without unlinking the device, so the next boot resumes from the store.
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
  process.on(signal, async () => {
    await client.disconnect()
    process.exit(0)
  })
}
logout() unlinks the device server-side and clears stored state — it forces a full re-pair. Use it only to permanently disconnect, never as a shutdown hook.

Run many sessions

A single store can hold many independent accounts, each keyed by sessionId. Create one WaClient per account:
const clients = tenants.map((id) => new WaClient({ store, sessionId: id }, logger))
await Promise.all(clients.map((c) => c.connect()))
Each client pairs, reconnects, and emits events independently. Budget memory/CPU per session (each holds Signal state and in-memory caches), and shard across processes/hosts as you scale — one process per session is the simplest isolation model. See multi-tenancy.

Tune for throughput

  • Write-behind batches incoming message/thread/contact writes off the hot path. Tune writeBehind.maxPendingKeys / maxWriteConcurrency / flushTimeoutMs to your database. (config)
  • History sync (history.enabled) adds a large initial download. Leave it off unless you need backfill, and set requireFullSync deliberately.
  • Bots that shouldn’t appear online: set markOnlineOnConnect: false.
  • Timeouts (iqTimeoutMs, keepAliveIntervalMs, deadSocketTimeoutMs, …) ship with production defaults — override only with a reason. (config)

Reconnection & error policy

zapo does not auto-reconnect — own the policy. Wire a backoff loop (Reconnection) and classify failures (Errors & disconnects) so you stop on fatal reasons (banned, not_authorized, logout) instead of hammering the server.

Logging

Use a structured logger in production:
const logger = await createPinoLogger({ level: 'info', pretty: false })
pretty: false emits JSON lines suited to log aggregators. Drop to debug / trace only when investigating.

Security & versioning

  • Credentials are secrets. WaAuthCredentials holds the device keys — if you persist them outside the built-in store, encrypt at rest. (Authentication)
  • Never enable dangerous.* in production — those flags disable security checks. (config)
  • Pin exact versions. zapo is pre-1.0; breaking changes are expected until the first major release.
Last modified on May 28, 2026