If you have ever tried running more than a handful of WhatsApp sessions on Baileys or whatsapp-web.js, you already know the story: memory climbs like a fever curve, one desync locks the pair state, PM2 restarts the Node process at 3AM, and you wake up to a dead campaign and 500 angry Slack messages.
I got tired of it. So I built Waxum — a WhatsApp REST API gateway written in native Rust.
One binary. 500 concurrent sessions. ~20 MB RAM. Zero garbage collector pauses. MIT.
Why another WhatsApp gateway?
Because the alternatives leave you paying with your ops budget:
| Baileys / whatsapp-web.js | Waxum | |
|---|---|---|
| Runtime | Node.js | Native Rust binary |
| Concurrent sessions per process (real production) | ~30-50 before OOM | 500+ verified in prod |
| RAM per idle instance | 200-800 MB | ~20 MB |
| Latency P99 (send text) | 40-120 ms with GC jitter | < 10 ms flat |
| Deploy artifact |
node_modules (450+ MB) |
single binary (~27 MB) |
| Webhook retry + circuit breaker | roll your own | built-in (25 fails = open, 100 = auto-disable) |
| OpenAPI schema | community forks | generated from code |
| Multi-DB | mongo/sqlite | Postgres · MySQL · SQLite |
| License | MIT / ISC | MIT |
Rust means no runtime dependencies, no GC, no require graph rebuild, no npm audit. It also means the binary Just Runs — scp it to a $5 VPS, register a systemd unit, and forget it exists.
What Waxum ships out of the box
The messaging surface
Every WhatsApp Web message type is a REST endpoint. Text, image, video, audio, document, sticker, location, contact, polls, legacy buttons, list messages, native-flow interactive (CTA URL, quick reply), reactions, edits, revokes, view-once, forwards, stars, pins, newsletter admin/follower invites, scheduled call cards, request/send/cancel payment. Around 30 endpoints total under /api/v1/sessions/{id}/messages/*.
curl -X POST http://localhost:3451/api/v1/sessions/my-session/messages/text \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"to":"628123456789","text":"Hello from Waxum"}'
Multi-session, isolated storage
Each session lives in its own storage directory. Create a session, pair it via QR or phone-code, and it stays isolated. Postgres, MySQL, or SQLite (zero-config default) handles the metadata layer.
curl -X POST http://localhost:3451/api/v1/sessions \
-H "Authorization: Bearer $TOKEN" \
-d '{"id":"acct-taqin","name":"Marketing"}'
Webhooks with a real circuit breaker
Every WhatsApp event fans out to your registered URLs with an HMAC-SHA256 signature. Waxum tracks per-URL failures and reacts in two stages:
- OPEN after 25 consecutive failures — dispatch is skipped for 5 minutes, log noise drops to a single warning.
-
Auto-disable after 100 failures — the row flips to
enabled=false,disabled_atanddisabled_reasonget stamped, and the dispatcher will not touch that URL until you flip it back withPOST /webhooks/{id}/enable.
No more orphan 127.0.0.1:3452 targets getting hammered for months after a colleague's laptop went offline.
Health probes for real orchestrators
-
GET /livez— pure static probe (Kubernetes liveness). -
GET /readyz— runsSELECT 1against the DB pool + reports session count in JSON. -
GET /metrics— Prometheus text exposition (JWT bypass):waxum_sessions_total,waxum_sessions_live,waxum_webhook_circuits_open,waxum_process_threads,waxum_process_open_fds.
Bearer JWT, superadmin token, per-session scopes
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Provision new tokens via superadmin, scope them per session, rotate at will. The auth middleware bypasses /livez, /readyz, /health, /metrics so probes never fight the auth wall.
NATS JetStream fan-out (optional)
Point NATS_URL at your JetStream and every event lands on wa.events.{session}.{type}. You can also send messages by publishing to wa.send.{session} — 16 supported message types, durable, replayable, queue-based.
nats pub "wa.send.acct-taqin" \
'{"type":"text","to":"628123456789","text":"via JetStream"}'
Skip the env var and Waxum runs in webhooks-only mode — no dependency.
Swagger UI
/swagger-ui mounts an interactive spec generated by utoipa straight from the Rust structs. Every request/response is typed. No hand-written schema drift.
Windows without VCRUNTIME140.dll pain
The Windows release binary is compiled against x86_64-pc-windows-gnu with a statically linked MinGW runtime. Ship it to a clean Windows Server and it runs. No "MSVC redistributable required" dialogs.
Install in one shot
# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/imtaqin/waxum/main/scripts/install.sh | sudo bash
# Windows (elevated PowerShell)
irm https://raw.githubusercontent.com/imtaqin/waxum/main/scripts/install.ps1 | iex
# Docker (multi-arch: amd64 + arm64)
docker pull fdciabdul/waxum
The installer drops the binary in /usr/local/bin, writes a systemd unit, generates a random JWT + superadmin token in /etc/waxum.env, and offers to enable a nightly auto-update cron. Yes, on Windows it registers a proper Service that survives reboots.
Or if you want to build from source:
git clone https://github.com/imtaqin/waxum && cd waxum
cargo build --release
./target/release/waxum
Docker Compose in 30 seconds
services:
api:
image: fdciabdul/waxum:latest
environment:
DATABASE_URL: postgres://waxum:waxum@db:5432/waxum
SUPERADMIN_TOKEN: change-me
ports: ["3451:3451"]
db:
image: postgres:16
environment:
POSTGRES_USER: waxum
POSTGRES_PASSWORD: waxum
POSTGRES_DB: waxum
docker compose up -d and hit http://localhost:3451/swagger-ui.
The stack
-
Rust nightly — for
let-elsepatterns,let-chains, and edition 2021 sugar - Axum 0.8 — Tokio-native HTTP framework
- Tokio full — multi-thread runtime, tuned for 200+ session workloads
- whatsapp-rust — the multi-device WA Web protocol client
- Tower / Tower-HTTP — middleware stack
- Serde + Utoipa — typed IO + OpenAPI
- DashMap + parking_lot — sharded in-memory state
-
prometheuscrate — no default features, zero cost when unscraped
Single binary is ~27 MB (release, LTO on). Startup to first-message-serviceable is under 200 ms even with 500 restored sessions.
Production numbers
Running for months on a bare-metal PM2 setup with a single waxum process:
- 500+ WhatsApp sessions live and reconnecting
-
~20 MB resident RAM at steady state (
ps aux | grep waxum) -
~30 threads total (
/proc/self/status) - P99 latency < 10 ms for text send end-to-end
- 1024 → 65536 FD limit raised because at 500 sessions you burn ~70 file descriptors each
-
Postgres backend — migrated from MySQL with
pg_dump | psqland oneSELECT setval()
That is production. Not a synthetic benchmark on a MacBook.
What is next
The v0.7 line is out and stable. On the roadmap:
- Deeper VoIP integration (upstream
whatsapp-rustshipped 1:1 voice calling, we already do signalling) - Hosted control panel with per-tenant billing
- Cluster mode — session sharding across N instances with NATS as the coordination bus
- Rich session snapshots / restore
Links
- Docs: waxum.imtaqin.id
- GitHub: github.com/imtaqin/waxum
- Docker Hub: hub.docker.com/r/fdciabdul/waxum
- Releases: github.com/imtaqin/waxum/releases
- License: MIT
If Baileys memory footprint has bit you at 3AM, or you just want a stable, boring, production-grade WhatsApp REST API you can scp to a $5 box, give Waxum a spin.
PRs, issues, and use-case stories welcome.
Top comments (0)