How to Manage Multiple WhatsApp Sessions Concurrently Using OpenWA

OpenWA enables concurrent WhatsApp automation through a session-centric architecture where each connection runs as an isolated engine instance managed by the SessionService, allowing you to create, monitor, and interact with unlimited sessions simultaneously via REST or WebSocket APIs.

The rmyndharis/OpenWA repository provides a production-ready Node.js solution for managing multiple WhatsApp sessions concurrently using OpenWA. By isolating each WhatsApp Web connection in its own engine instance while persisting session metadata to PostgreSQL, the framework ensures that operations on one session never interfere with others, making it ideal for multi-tenant applications and high-scale messaging platforms.

Understanding OpenWA's Concurrent Session Architecture

At the heart of the system is the SessionService located in src/modules/session/session.service.ts. This service maintains a private in-memory Map that stores live engine instances: private engines: Map<string, IWhatsAppEngine>, where each key represents a unique session ID and the value is a fully initialized WhatsApp engine.

The EngineFactory in src/engine/engine.factory.ts implements a factory pattern to instantiate concrete engine implementations. Currently, it produces whatsapp-web-js adapters that implement the IWhatsAppEngine interface defined in src/engine/interfaces/whatsapp-engine.interface.ts. This abstraction allows each session to operate with its own browser context, authentication state, and event handlers, ensuring true process isolation at the application layer.

Session persistence is handled via TypeORM, with the Session entity in src/modules/session/entities/session.entity.ts storing configuration, status, and metadata in PostgreSQL. This separation between persistent state (database) and runtime state (in-memory Map) enables the system to restore sessions after restarts while maintaining active connection pools only for running sessions.

Creating and Configuring New Sessions

To add a new concurrent session, you insert a record into the database via the REST API. The CreateSessionDto in src/modules/session/dto/create-session.dto.ts defines the expected payload, including session-specific reconnection parameters.

POST /sessions
Content-Type: application/json

{
  "name": "store-location-1",
  "config": {
    "maxReconnectAttempts": 8,
    "reconnectBaseDelay": 4000
  }
}

Internally, SessionService.create() writes the record and emits the session:created hook. At this stage, the session exists in the database with status CREATED, but no engine instance occupies memory in the engines Map.

Starting Sessions and Initializing Engines

Activation occurs through the start endpoint, which triggers the full lifecycle initialization:

POST /sessions/{sessionId}/start

The SessionService.start() method creates a ReconnectState object for the session, then calls initializeEngine() to instantiate a new engine via the EngineFactory. The factory injects session-specific configuration—including proxy settings and browser options—into the whatsapp-web-js adapter located at src/plugins/engines/whatsapp-web-js/index.ts.

Once initialized, the engine generates a QR code for authentication. The service captures this through the onQRCode callback and broadcasts it via the EventsGateway in src/modules/events/events.gateway.ts, which pushes real-time updates to connected WebSocket clients.

import { OpenWAClient } from '@openwa/sdk';

const client = new OpenWAClient({ baseUrl: 'http://localhost:3000' });

await client.sessions.start(sessionId);
const { qrCode, status } = await client.sessions.getQRCode(sessionId);
// Render qrCode (base64 PNG) for user scanning

Routing Messages to Specific Sessions

When sending messages, the system routes requests to the correct engine using the session ID. The MessageService resolves the appropriate instance via this.sessionService.getEngine(sessionId), ensuring that "Hello from session A" never routes through session B's connection.

POST /sessions/{sessionId}/messages
Content-Type: application/json

{
  "to": "628123456789",
  "body": "Hello from session 1!"
}
await client.sessions.messages.send(sessionId, {
  to: '628123456789',
  body: 'Hello from session 1!'
});

This isolation extends to all engine operations—including getGroups, getContacts, and media handling—preventing session data leakage across tenant boundaries.

Handling Reconnection and Session Resilience

Each concurrent session maintains independent reconnection logic stored in the reconnectStates Map within SessionService. When a whatsapp-web-js engine disconnects, the service evaluates the session's maxReconnectAttempts and reconnectBaseDelay configuration to execute an exponential backoff strategy.

If reconnection succeeds, the engine re-enters the READY state without affecting other sessions. If attempts exhaust, the status updates to FAILED and the engine instance is removed from the Map, freeing memory while preserving session history in PostgreSQL.

Monitoring Session Health and Performance

The service exposes aggregate metrics for observability dashboards. Calling SessionService.getStats() returns active counts, memory usage, and status distributions across all sessions.

GET /sessions/stats
const stats = await client.sessions.getStats();
console.log('Active engines:', stats.active);
console.log('Ready sessions:', stats.ready);
console.log('Memory (MB):', stats.memoryUsage);

For real-time monitoring, the EventsGateway streams session-level events—session:qr, session:ready, message:received—to frontend clients, while the optional WebhookService in src/modules/webhook/webhook.service.ts forwards these events to external HTTP endpoints per session configuration.

Graceful Session Termination

To stop a specific session without impacting others:

POST /sessions/{sessionId}/stop
await client.sessions.stop(sessionId);

SessionService.stop() cancels pending reconnection timers, calls engine.destroy(), removes the entry from the engines Map, and updates the database status to DISCONNECTED. This clean teardown prevents memory leaks when managing high-churn session pools.

Summary

  • SessionService in src/modules/session/session.service.ts orchestrates concurrent sessions using an in-memory Map of IWhatsAppEngine instances, ensuring isolation between connections.
  • Each session receives its own engine via the EngineFactory, allowing independent configuration of proxies, reconnection policies, and browser contexts.
  • The REST API provides endpoints for creating (POST /sessions), starting (POST /sessions/:id/start), messaging (POST /sessions/:id/messages), and monitoring (GET /sessions/stats) individual sessions.
  • Real-time events flow through EventsGateway and optionally WebhookService, enabling reactive UIs and external integrations per session.
  • Reconnection handling is session-specific with exponential backoff, configured via maxReconnectAttempts and reconnectBaseDelay parameters.

Frequently Asked Questions

How many concurrent WhatsApp sessions can OpenWA handle?

The theoretical limit depends on available system memory and CPU, as each session maintains a separate whatsapp-web-js engine instance in the engines Map. In production deployments, organizations typically run hundreds of concurrent sessions per instance by tuning Node.js memory limits and using appropriate proxy rotation to prevent rate limiting.

What database does OpenWA use for session persistence?

OpenWA uses PostgreSQL via TypeORM to persist session metadata, configuration, and status history in the Session entity. While runtime engine objects live in memory, the database ensures that session records survive application restarts and enable horizontal scaling behind load balancers.

How does OpenWA isolate sessions to prevent cross-contamination?

Session isolation occurs at the architecture level: the SessionService stores each engine as a distinct value in a Map<string, IWhatsAppEngine> keyed by session ID. The EngineFactory creates separate browser contexts for each session, and the MessageService explicitly resolves engines via getEngine(sessionId), ensuring that authentication tokens, message queues, and event handlers never overlap between sessions.

Can I configure different proxy settings for each concurrent session?

Yes, proxy configuration is passed per-session to the EngineFactory during initializeEngine(). When creating a session via POST /sessions, include proxy details in the config object, and the factory will instantiate the whatsapp-web-js adapter with those specific network settings, allowing geographically distributed sessions from a single OpenWA instance.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →