Architecture

Ruby app
    │
    ├── HTTP  ──► commands (send, status, session...)
    └── WS    ◄── real-time events (messages, QR, connection...)
            │
            ▼
    Node.js bridge (Fastify + Baileys)
            │
            ▼
    WhatsApp Web (WebSocket)

The gem spawns a local Node.js process running a Fastify HTTP + WebSocket server wrapping Baileys. All communication stays on 127.0.0.1 with a dynamic port. Sessions are persisted to disk so you only need to scan the QR code once.

In multi-session mode, each session gets its own bridge process, dynamic port, and auth directory (auth_dir/SessionName/).

Key design decisions

Decision Why
Bridge over FFI Baileys is JS-only — a bridge is the only clean option
Fastify (not Express) Faster, built-in schema validation, native WebSocket
HTTP + WebSocket HTTP for request/response, WS for real-time events
Dynamic port No conflicts, supports multiple sessions
1 session = 1 bridge Full isolation per WhatsApp account
Baileys pinned to commit npm releases lag behind, protocol changes break things
Double rate limiting Ruby controls the flow, bridge is the safety net
Gaussian jitter Random delays that look human, not uniform bot patterns

Class overview

Class Role
Whatsapp::Client Core client — connect, send, events
Whatsapp::Session Named session wrapping a Client (status, QR, heartbeat)
Whatsapp::SessionManager Thread-safe session registry
Whatsapp::AccountProxy Proxy for account: true mode
Whatsapp::RecordProxy Proxy for destination mode (has_whatsapp :phone)
Whatsapp::Bridge HTTP + WebSocket communication with Node bridge
Whatsapp::Connection Spawns/kills the Node bridge process
Whatsapp::Heartbeat Periodic health check thread
Whatsapp::RateLimiter Ruby-side rate limiting with gaussian jitter
Whatsapp::MessageHandler Base class for incoming message handlers
Whatsapp::MessageDispatcher Dispatches messages to configured handler
Whatsapp::Notifier Notification service with templates and bulk
Whatsapp::Events Event emitter
Whatsapp::Message Parsed incoming message