DEV Community

Cover image for Waxum: The Fastest WhatsApp REST API Gateway — 500 Sessions in 20MB RAM, Written in Rust
Taqin
Taqin

Posted on • Originally published at waxum.imtaqin.id

Waxum: The Fastest WhatsApp REST API Gateway — 500 Sessions in 20MB RAM, Written in Rust

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"}'
Enter fullscreen mode Exit fullscreen mode

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"}'
Enter fullscreen mode Exit fullscreen mode

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_at and disabled_reason get stamped, and the dispatcher will not touch that URL until you flip it back with POST /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 — runs SELECT 1 against 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...
Enter fullscreen mode Exit fullscreen mode

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"}'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

docker compose up -d and hit http://localhost:3451/swagger-ui.

The stack

  • Rust nightly — for let-else patterns, 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
  • prometheus crate — 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 | psql and one SELECT 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-rust shipped 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

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)