Overview & Setup
Introduction
Section titled “Introduction”The WhatsApp Web Server exposes a REST API and WebSocket interface for automating WhatsApp Web clients via the Baileys library. It replaces the legacy Electron IPC bridge with an HTTP-first architecture plus event streaming, and persists chats, messages, and contacts in a local SQLite database by default.
- API surface: REST endpoints for client lifecycle, conversations, contacts, and messaging.
- Real-time updates: WebSocket events for QR codes, readiness, new messages, and sync completion.
- Persistence: SQLite out of the box, with optional Redis, PostgreSQL, MongoDB, or filesystem adapters.
Getting Started
Section titled “Getting Started”Install dependencies
Section titled “Install dependencies”cd apps/whatsapp-web-serverpnpm installRecommended: Docker-backed development
Section titled “Recommended: Docker-backed development”# copy the default environment templatecp .env.example .env
# launch the server plus supporting servicesbun run devRunning bun run dev automatically provisions the following containers:
- PostgreSQL (
localhost:5433) — PgAdmin UI athttp://localhost:8080 - MongoDB (
localhost:27018) — Mongo Express athttp://localhost:8082 - Redis (
localhost:6380) — Redis Commander athttp://localhost:8083 - SQLite Browser at
http://localhost:8084
Docker helper scripts
Section titled “Docker helper scripts”bun run docker:up # start every servicebun run docker:down # stop all servicesbun run docker:restart # restart the stackbun run docker:logs # tail container logsbun run docker:status # check container statusbun run docker:postgres # start PostgreSQL onlybun run docker:mongo # start MongoDB onlybun run docker:redis # start Redis onlybun run docker:clean # remove containers + volumesManual setup (without Docker)
Section titled “Manual setup (without Docker)”Create a .env file in the workspace root and provide the required configuration:
# ServerNODE_ENV=developmentPORT=3001HOST=localhost
# Persistence selector (choose one)STORAGE_TYPE=memory# STORAGE_TYPE=file# STORAGE_TYPE=redis# STORAGE_TYPE=postgres# STORAGE_TYPE=mongodb
# File storageFILE_STORAGE_PATH=./auth_data
# RedisREDIS_HOST=localhostREDIS_PORT=6379REDIS_PASSWORD=REDIS_DB=0
# PostgreSQLPOSTGRES_URL=postgresql://localhost:5432/whatsapp_authPOSTGRES_HOST=localhostPOSTGRES_PORT=5432POSTGRES_DB=whatsapp_authPOSTGRES_USER=postgresPOSTGRES_PASSWORD=
# MongoDBMONGODB_URL=mongodb://localhost:27017/whatsapp_auth
# WebSocketWEBSOCKET_ENABLED=trueWEBSOCKET_PORT=3002
# WhatsApp clientWA_BROWSER_NAME=WhatsApp Web ServerWA_BROWSER_VERSION=1.0.0WA_SESSION_TIMEOUT=60000WA_MAX_RECONNECT_ATTEMPTS=5
# SecurityAPI_KEY=CORS_ORIGINS=http://localhost:3000,http://localhost:5173
# LoggingLOG_LEVEL=infoStart the server:
# development modepnpm dev
# production buildpnpm buildpnpm start- REST API:
http://localhost:3001 - WebSocket:
ws://localhost:3002
Environment Configuration
Section titled “Environment Configuration”Configuration is validated via envalid during startup. Select the storage back end that best matches your scenario:
| Storage type | Description | Ideal usage |
|---|---|---|
sqlite | Bundled SQLite database (default) | Development or production with local persistence |
memory | In-memory store | Tests or ephemeral sessions |
file | File-system backed | Single host, simple persistence |
redis | Redis instance | Distributed workloads, caching |
postgres | PostgreSQL database | Production with relational guarantees |
mongodb | MongoDB collection | Production document store |
Database workflows
Section titled “Database workflows”SQLite
Section titled “SQLite”pnpm db:init # initialize the databasepnpm db:generate # create new migrationspnpm db:studio # open Drizzle StudioWith Docker, inspect the data through the SQLite Browser (http://localhost:8084). Stored entities include contacts, conversations, messages, message reactions, receipts, group membership, and media metadata. Data lives at ./db/whatsapp.db by default (override using SQLITE_DB_PATH).
PostgreSQL
Section titled “PostgreSQL”The server provisions the following table automatically when STORAGE_TYPE=postgres:
CREATE TABLE auth_sessions ( account_number VARCHAR(50) PRIMARY KEY, auth_state JSONB NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW());MongoDB
Section titled “MongoDB”When STORAGE_TYPE=mongodb, the application creates the auth_sessions collection with the schema required to house session documents.
Authentication Flow
Section titled “Authentication Flow”-
Initialize the client
POST /api/clients/initializeContent-Type: application/json{"accountNumber": "your-phone-number","forceRecreate": false} -
Fetch the QR code
- WebSocket
qrevent, or - REST poll:
GET /api/clients/{accountNumber}/qr
- WebSocket
-
Scan the QR code using the WhatsApp mobile app.
-
Wait for readiness
- WebSocket
readyevent, or - REST status:
GET /api/clients/{accountNumber}/status
- WebSocket
-
Call other endpoints for conversations, contacts, or messaging once ready.
API Reference
Section titled “API Reference”Base URL: http://localhost:3001/api
If API_KEY is configured, include Authorization: Bearer <token> on every request.
Client management
Section titled “Client management”| Endpoint | Method | Purpose |
|---|---|---|
/clients/initialize | POST | Create or rehydrate a client session |
/clients/{accountNumber}/status | GET | Inspect connection state |
/clients/{accountNumber}/qr | GET | Retrieve the current QR code |
/clients/{accountNumber}/disconnect | DELETE | Terminate the client session |
/clients/status | GET | List all client sessions |
Conversations
Section titled “Conversations”GET /conversations/{accountNumber}?limit=20&offset=0Returns conversation metadata, last message previews, unread counts, and pagination totals.
GET /conversations/{accountNumber}/{chatId}Fetch detailed chat information including participants and descriptions.
Messages
Section titled “Messages”POST /messages/sendContent-Type: application/json
{ "accountNumber": "your-phone-number", "chatId": "1234567890@c.us", "message": { "text": "Hello, World!" }}Media payloads are supported by providing image, audio, or video objects with URLs plus optional captions.
GET /messages/{accountNumber}/{chatId}?limit=20&before=msg_idReturns paginated message history, including message type, delivery status, and timestamps.
Contacts
Section titled “Contacts”GET /contacts/{accountNumber}?limit=100&search=johnResponds with contact identifiers, names, numbers, profile photos, and presence details.
WebSocket Events
Section titled “WebSocket Events”Connect to ws://localhost:3002 to stream real-time events.
const ws = new WebSocket('ws://localhost:3002')
ws.onmessage = (event) => { const { event: eventType, data } = JSON.parse(event.data)
switch (eventType) { case 'qr': // Render QR code for the operator break case 'ready': // Client is authenticated and ready break case 'message': // Process incoming message break default: break }}Event payloads
Section titled “Event payloads”-
qr{"event": "qr","data": {"clientId": "your-phone-number","qr": "QR_CODE_STRING"}} -
ready{"event": "ready","data": {"clientId": "your-phone-number","info": {"number": "1234567890","name": "Your Name"}}} -
message{"event": "message","data": {"clientId": "your-phone-number","message": {"id": "message_id","from": "sender@c.us","body": "Hello!","timestamp": 1234567890}}}
Additional events like sync-complete, connection-state, or webhook notifications follow the same shape.
End-to-end example
Section titled “End-to-end example”// 1. Initialize a client sessionawait fetch('http://localhost:3001/api/clients/initialize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accountNumber: 'your-phone-number' })})
// 2. Subscribe to eventsconst ws = new WebSocket('ws://localhost:3002')
ws.onmessage = async (event) => { const { event: type, data } = JSON.parse(event.data)
if (type === 'qr') { displayQRCode(data.qr) }
if (type === 'ready') { // 3. Query stored data once sync finishes const conversationsResponse = await fetch( 'http://localhost:3001/api/conversations?accountNumber=your-phone-number' ) const contactsResponse = await fetch( 'http://localhost:3001/api/contacts?accountNumber=your-phone-number' )
console.log('Conversations', (await conversationsResponse.json()).data.conversations.length) console.log('Contacts', (await contactsResponse.json()).data.contacts.length)
// 4. Send a message await fetch('http://localhost:3001/api/messages/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accountNumber: 'your-phone-number', chatId: 'recipient@c.us', message: { text: 'Hello from the database-backed server!' } }) }) }}Database schema highlights
Section titled “Database schema highlights”The SQLite-backed store organizes WhatsApp data into normalized tables:
accounts— WhatsApp account metadatacontacts— profile details, numbers, profile pictures, and statuschats— conversation metadata and settingsmessages— message payloads, timestamps, and linkage to chatsmessage_content— parsed text for searchabilitygroup_participants— group membership and rolesmessage_reactions— emoji reactionsuser_receipts— read/delivery receiptsmedia_data— media metadata and storage references
The schema materializes automatically through Drizzle migrations and stays in sync with upstream WhatsApp data via messaging-history.set events.
Related documentation
Section titled “Related documentation”- Testing & CI — Run unit tests, enforce coverage, and integrate with CI pipelines.
- Repository CI workflow — Full matrix-based pipeline configuration.