Production-ready, self-hosted WhatsApp Web API with Admin UI, durable queues, and automation hooks
Watson WA API is a self-hosted WhatsApp integration platform built on WhatsApp Web using Baileys.
It provides a secure API, Admin UI, Pairing UI, durable outbound queues, and optional n8n automation forwarding.
Designed for single-server production deployments, with safety-first throttling and persistent message delivery.
- Features
- Architecture
- Screens & UIs
- Repository Structure
- Security Model
- Requirements
- Quick Start
- Environment Variables
- API Overview
- Aliases
- Durable Queue
- n8n Automations
- Re-Pairing WhatsApp
- Backups
- Security Notes
- Production Checklist
- License
- WhatsApp Web connection via Baileys
- QR-based Pairing UI
- Web Admin UI
- Integration-friendly REST API
- Durable outbound queue using Redis + BullMQ
- Strong built-in throttling to reduce ban risk
- Signed media URLs for secure previews
- Optional PostgreSQL-backed settings and message storage
- Contact & group alias management
- Chat history browsing
- Optional n8n webhook forwarding
- Docker + nginx deployment ready
Internet
│
▼
nginx (TLS + Basic Auth)
│
▼
wa-api (Node.js / Express / Baileys)
│
├── Redis → durable outbound queue
├── PostgreSQL → optional runtime settings + messages
├── auth/ → WhatsApp session
├── data/ → contacts, automations (and JSON fallback)
└── uploads/ → media files
- QR code login
- One-time device linking
- Session persisted to disk
- Send messages
- Manage contacts & groups
- Configure automations
- View chat history
Tip: Protect both with nginx Basic Auth in production.
wa-api/
├─ server.js
├─ package.json
├─ Dockerfile
├─ docker-compose.yml
├─ .env.example
├─ ui/
│ ├─ admin/
│ └─ pairing/
├─ nginx/
│ ├─ conf.d/
│ └─ auth/
└─ README.md
| Area | Protection |
|---|---|
| Pairing UI | nginx Basic Auth |
| Admin UI/API | x-admin-key |
| Integration API | x-api-key |
| Media Files | Signed URLs |
| Redis | Internal only |
- Docker
- Docker Compose
- Server with outbound internet access
- WhatsApp account (phone required)
git clone https://github.com/Naude555/watson.git
cd wa-apicp .env.example .envEdit:
WA_API_KEY=change_me
WA_ADMIN_KEY=change_me
MEDIA_SIGNING_SECRET=long_random_value
NODE_ENV=productionmkdir -p nginx/auth
docker run --rm -it -v "$PWD/nginx/auth:/auth" httpd:2.4-alpine sh -lc "apk add --no-cache apache2-utils && htpasswd -c /auth/.htpasswd admin"docker compose up -d --buildOpen:
http://<server>/pairing/ui
Scan QR in WhatsApp → Linked Devices.
| Variable | Description |
|---|---|
| PORT | Internal port (default 3000) |
| NODE_ENV | production |
| Variable | Description |
|---|---|
| WA_API_KEY | Integration API key |
| WA_ADMIN_KEY | Admin API/UI key |
| MEDIA_SIGNING_SECRET | Media URL signing |
| Variable | Description |
|---|---|
| REDIS_URL | Redis connection string |
| WA_QUEUE_NAME | Queue name |
| Variable | Description |
|---|---|
| STORAGE_BACKEND | postgres or sqlite (default sqlite) |
| DATABASE_URL | PostgreSQL connection string |
| PG_HOST | PostgreSQL host (if not using DATABASE_URL) |
| PG_PORT | PostgreSQL port (default 5432) |
| PG_DATABASE | PostgreSQL database name |
| PG_USER | PostgreSQL username |
| PG_PASSWORD | PostgreSQL password |
| PG_SSL | Set true to enable SSL mode |
| SQLITE_FILE | SQLite database file path for fallback/default |
When STORAGE_BACKEND=postgres, the app uses PostgreSQL if configured (DATABASE_URL or PG_*).
If PostgreSQL is not configured or init fails, it falls back to SQLite automatically.
In DB-backed mode:
- runtime settings are stored in the active DB backend (PostgreSQL or SQLite)
- messages are persisted to the active DB backend (PostgreSQL or SQLite)
Runtime controls in Admin UI → Operations:
messages.uiFetchLimit(default500)messages.persistMax(0means save everything)media.retentionDays(default7)
WA_BASE_DELAY_MS=1200
WA_JITTER_MS=800
WA_PER_JID_GAP_MS=3000
WA_MAX_RETRIES=5
WA_RETRY_BACKOFF_MS=2000GET /health
x-api-key: <key>POST /send{
"to": "2782xxxxxxx",
"message": "Hello"
}Multipart:
- to
- image
- caption (optional)
{
"to": "2782xxxxxxx",
"imageUrl": "https://...",
"caption": "Hello"
}- Multipart or URL mode
Raw WhatsApp IDs are unreadable:
1203xxx@g.us
2782xxx@s.whatsapp.net
Aliases let you use human names instead.
Managed in Admin UI → Contacts / Groups.
Outbound messages are stored in Redis via BullMQ.
Benefits:
- Survives restarts
- Persistent retries
- No message loss
- Per-recipient throttling
Concurrency = 1 for account safety.
Enable:
N8N_WEBHOOK_URL=https://...
N8N_SHARED_SECRET=secretForwarded when rules allow.
Header:
x-watson-secret: <secret>
docker compose down
rm -rf ./auth/*
docker compose up -dRe-scan QR.
Back up:
- auth/
- data/
- uploads/
- Never expose Redis publicly
- Never commit
.envorauth/ - Rotate keys if leaked
- Use HTTPS
- Enable Basic Auth
- Secrets configured
- Redis volume enabled
- TLS enabled
- Basic Auth enabled
- Throttling configured
- Health endpoint OK
- WhatsApp paired
- Backups configured
MIT
This system is intentionally conservative:
- Human-like send rates
- Single-threaded queue
- Durability over speed
That’s how WhatsApp accounts survive in production.