NexisChat Docs
WhatsApp Web Server

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 install
# copy the default environment template
cp .env.example .env

# launch the server plus supporting services
bun run dev

Running bun run dev automatically provisions the following containers:

  • PostgreSQL (localhost:5433) — PgAdmin UI at http://localhost:8080
  • MongoDB (localhost:27018) — Mongo Express at http://localhost:8082
  • Redis (localhost:6380) — Redis Commander at http://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 + volumes

Manual 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=info

Start 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 typeDescriptionIdeal usage
sqliteBundled SQLite database (default)Development or production with local persistence
memoryIn-memory storeTests or ephemeral sessions
fileFile-system backedSingle host, simple persistence
redisRedis instanceDistributed workloads, caching
postgresPostgreSQL databaseProduction with relational guarantees
mongodbMongoDB collectionProduction document store

Database workflows

SQLite

pnpm db:init      # initialize the database
pnpm db:generate  # create new migrations
pnpm db:studio    # open Drizzle Studio

With 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

  1. Initialize the client

    POST /api/clients/initialize
    Content-Type: application/json
    
    {
      "accountNumber": "your-phone-number",
      "forceRecreate": false
    }
  2. Fetch the QR code

    • WebSocket qr event, or
    • REST poll: GET /api/clients/{accountNumber}/qr
  3. Scan the QR code using the WhatsApp mobile app.

  4. Wait for readiness

    • WebSocket ready event, or
    • REST status: GET /api/clients/{accountNumber}/status
  5. 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

EndpointMethodPurpose
/clients/initializePOSTCreate or rehydrate a client session
/clients/{accountNumber}/statusGETInspect connection state
/clients/{accountNumber}/qrGETRetrieve the current QR code
/clients/{accountNumber}/disconnectDELETETerminate the client session
/clients/statusGETList all client sessions

Conversations

GET /conversations/{accountNumber}?limit=20&offset=0

Returns 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_id

Returns paginated message history, including message type, delivery status, and timestamps.

Contacts

GET /contacts/{accountNumber}?limit=100&search=john

Responds 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 metadata
  • contacts — profile details, numbers, profile pictures, and status
  • chats — conversation metadata and settings
  • messages — message payloads, timestamps, and linkage to chats
  • message_content — parsed text for searchability
  • group_participants — group membership and roles
  • message_reactions — emoji reactions
  • user_receipts — read/delivery receipts
  • media_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.