Overview & Setup
How to install, configure, and use the WhatsApp Web Server workspace, including environment options, API usage, and examples.
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
Install dependencies
cd apps/whatsapp-web-server
pnpm installRecommended: Docker-backed development
# copy the default environment template
cp .env.example .env
# launch the server plus supporting services
bun 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
bun run docker:up # start every service
bun run docker:down # stop all services
bun run docker:restart # restart the stack
bun run docker:logs # tail container logs
bun run docker:status # check container status
bun run docker:postgres # start PostgreSQL only
bun run docker:mongo # start MongoDB only
bun run docker:redis # start Redis only
bun run docker:clean # remove containers + volumesManual setup (without Docker)
Create a .env file in the workspace root and provide the required configuration:
# Server
NODE_ENV=development
PORT=3001
HOST=localhost
# Persistence selector (choose one)
STORAGE_TYPE=memory
# STORAGE_TYPE=file
# STORAGE_TYPE=redis
# STORAGE_TYPE=postgres
# STORAGE_TYPE=mongodb
# File storage
FILE_STORAGE_PATH=./auth_data
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# PostgreSQL
POSTGRES_URL=postgresql://localhost:5432/whatsapp_auth
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=whatsapp_auth
POSTGRES_USER=postgres
POSTGRES_PASSWORD=
# MongoDB
MONGODB_URL=mongodb://localhost:27017/whatsapp_auth
# WebSocket
WEBSOCKET_ENABLED=true
WEBSOCKET_PORT=3002
# WhatsApp client
WA_BROWSER_NAME=WhatsApp Web Server
WA_BROWSER_VERSION=1.0.0
WA_SESSION_TIMEOUT=60000
WA_MAX_RECONNECT_ATTEMPTS=5
# Security
API_KEY=
CORS_ORIGINS=http://localhost:3000,http://localhost:5173
# Logging
LOG_LEVEL=infoStart the server:
# development mode
pnpm dev
# production build
pnpm build
pnpm start- REST API:
http://localhost:3001 - WebSocket:
ws://localhost:3002
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
SQLite
pnpm db:init # initialize the database
pnpm db:generate # create new migrations
pnpm 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
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
When STORAGE_TYPE=mongodb, the application creates the auth_sessions collection with the schema required to house session documents.
Authentication Flow
-
Initialize the client
POST /api/clients/initialize Content-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
Base URL: http://localhost:3001/api
If API_KEY is configured, include Authorization: Bearer <token> on every request.
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
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
POST /messages/send
Content-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
GET /contacts/{accountNumber}?limit=100&search=johnResponds with contact identifiers, names, numbers, profile photos, and presence details.
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
-
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
// 1. Initialize a client session
await fetch('http://localhost:3001/api/clients/initialize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accountNumber: 'your-phone-number' })
})
// 2. Subscribe to events
const 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
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
- Testing & CI — Run unit tests, enforce coverage, and integrate with CI pipelines.
- Repository CI workflow — Full matrix-based pipeline configuration.