Skip to content

rafa-rrayes/whatsapp-hub

Repository files navigation

WhatsApp Hub

Your personal WhatsApp backbone. A single self-hosted service that maintains a persistent WhatsApp connection and exposes everything through a clean REST API, real-time WebSocket stream, and webhook system.

Every message, media file, contact, group, call, presence change, and status update is captured and stored in a local SQLite database. Connect all your projects — AI agents, auto-reply bots, dashboards, analytics — to one hub.

WhatsApp  <-->  Baileys Connection  <-->  Event Bus  <-->  SQLite DB
                                            |
                                    REST API + WebSocket
                                            |
                                     Webhook Dispatcher
                                            |
                                   Your projects subscribe

Features

  • Full message capture — text, images, video, audio, documents, stickers, locations, contacts, reactions, polls, view-once, forwards, quotes, edits, deletes
  • Media auto-download — organized by date in data/media/
  • Contacts & groups — names, profile pics, participants, roles, invite codes
  • Presence tracking — online/offline/typing/recording status log
  • Call log — incoming/outgoing, video/voice, duration
  • Status/Stories — captured and stored
  • Message receipts — sent/delivered/read/played timestamps per recipient
  • Webhook system — HMAC-signed payloads, event filtering, toggle on/off, SSRF protection
  • WebSocket streaming — real-time events with optional event type filtering, ticket-based auth
  • Full-text search — search across all messages
  • Web dashboard — 10-page interactive UI for browsing everything
  • Security hardening — database encryption at rest, webhook secret encryption, configurable security toggles
  • Docker-ready — single docker compose up to deploy

Quick Start

1. Clone & configure

git clone https://github.com/rafa-rrayes/whatsapp-hub.git
cd whatsapp-hub
cp .env.example .env

Edit .env and set a strong API key:

# Generate a random key
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"

2. Run with Docker Compose

docker compose up -d

3. Authenticate

Open http://localhost:3100 in your browser, enter your API key, and scan the QR code with WhatsApp.

Or check the container logs:

docker compose logs -f

4. Start using the API

# Check connection status
curl -H "x-api-key: YOUR_KEY" http://localhost:3100/api/connection/status

# Search messages
curl -H "x-api-key: YOUR_KEY" "http://localhost:3100/api/messages/search?q=hello"

# Send a message
curl -X POST -H "x-api-key: YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"jid": "[email protected]", "text": "Hello from WhatsApp Hub!"}' \
  http://localhost:3100/api/actions/send/text

API Reference

Interactive API docs are available in the dashboard at GET /api.

Authentication

All requests require an API key via one of:

  • Header: x-api-key: YOUR_KEY (recommended)
  • Header: Authorization: Bearer YOUR_KEY
  • Query param: ?api_key=YOUR_KEY (disabled when SECURITY_DISABLE_HTTP_QUERY_AUTH=true)

Endpoints

Connection
Method Endpoint Description
GET /api/connection/status Connection status & JID
GET /api/connection/qr QR code as base64 data URL
GET /api/connection/qr/image QR code as PNG
POST /api/connection/restart Restart connection
POST /api/connection/new-qr Clear session, generate new QR
POST /api/connection/logout Logout
Messages
Method Endpoint Description
GET /api/messages Query messages with filters
GET /api/messages/search?q= Full-text search
GET /api/messages/stats Statistics & breakdown
GET /api/messages/:id Single message by ID

Query parameters for /api/messages:

Param Description
chat Filter by chat JID
from Filter by sender JID
from_me true / false
type Message type (text, image, video, audio, document, sticker, etc.)
search Text search in message body
before / after Unix timestamp range
has_media true / false
limit / offset Pagination (default: 50)
order asc or desc (default: desc)
Chats
Method Endpoint Description
GET /api/chats List all chats (sorted by last message)
GET /api/chats/:jid Chat details + recent messages
Contacts
Method Endpoint Description
GET /api/contacts List all contacts
GET /api/contacts/:jid Single contact
GET /api/contacts/:jid/profile-pic Profile picture URL
Groups
Method Endpoint Description
GET /api/groups List all groups
GET /api/groups/:jid Group + participants
GET /api/groups/:jid/metadata Fresh metadata from WhatsApp
GET /api/groups/:jid/invite-code Invite code
PUT /api/groups/:jid/subject Update subject
PUT /api/groups/:jid/description Update description
POST /api/groups/:jid/participants Manage members
POST /api/groups/sync Sync all groups from WhatsApp
Actions (Send)
Method Endpoint Body
POST /api/actions/send/text { jid, text, quoted_id? }
POST /api/actions/send/image { jid, base64|url, caption?, mime_type? }
POST /api/actions/send/video { jid, base64|url, caption? }
POST /api/actions/send/audio { jid, base64|url, ptt? }
POST /api/actions/send/document { jid, base64|url, filename, mime_type, caption? }
POST /api/actions/send/sticker { jid, base64|url }
POST /api/actions/send/location { jid, latitude, longitude, name?, address? }
POST /api/actions/send/contact { jid, contact_jid, name }
POST /api/actions/react { jid, message_id, emoji }
POST /api/actions/read { jid, message_ids[] }
POST /api/actions/presence { type, jid? }
PUT /api/actions/profile-status { status }
Media
Method Endpoint Description
GET /api/media/stats Media statistics
GET /api/media/:id Media metadata
GET /api/media/:id/download Download file
GET /api/media/by-message/:msgId Get media by message ID
Webhooks
Method Endpoint Description
GET /api/webhooks List subscriptions
POST /api/webhooks Create { url, secret?, events? }
DELETE /api/webhooks/:id Delete
PUT /api/webhooks/:id/toggle Toggle active

Webhook payloads include X-Hub-Event and X-Hub-Signature (HMAC-SHA256) headers.

WebSocket

Connect to ws://your-server:3100/ws for real-time events. Max 20 concurrent connections with automatic ping/pong cleanup.

Authentication (one of):

Method Usage
Ticket (recommended) POST /api/ws/ticket → connect with ?ticket=TOKEN
Header x-api-key: YOUR_KEY (non-browser clients)
Query param (legacy) ?api_key=YOUR_KEY

Ticket auth requires SECURITY_WS_TICKET_AUTH=true. Tickets are one-time use and expire after 30 seconds.

Optional event filter: ?ticket=TOKEN&events=wa.messages.upsert,wa.presence.update

Settings
Method Endpoint Description
GET /api/settings List runtime settings with defaults
PUT /api/settings Update settings { logLevel?, autoDownloadMedia?, maxMediaSizeMB? }
Stats & Events
Method Endpoint Description
GET /api/stats Full dashboard overview
GET /api/stats/events Query event audit log
GET /api/stats/events/types Event type counts
DELETE /api/stats/events/prune?days=30 Prune old events

What Gets Stored

Category Details
Messages Text, images, video, audio, documents, stickers, locations, contacts, reactions, polls, view-once, forwarded, quoted, edits, deletes
Media Auto-downloaded to data/media/ in date-organized folders
Contacts Names, phone numbers, profile pics, business status
Groups Metadata, descriptions, participants, roles, invite codes
Chats Archive/pin/mute status, unread counts, last message preview
Receipts Sent, delivered, read, played timestamps per recipient
Presence Online/offline/typing/recording status log
Calls Incoming/outgoing, video/voice, status, duration
Stories Status updates captured and stored
Labels WhatsApp Business labels
Events Full audit trail with timestamps

Example Integrations

Python AI agent

import requests

API = "http://localhost:3100/api"
HEADERS = {"x-api-key": "YOUR_KEY"}

# Get unread chats
chats = requests.get(f"{API}/chats", headers=HEADERS).json()
unread = [c for c in chats["data"] if c["unread_count"] > 0]

# Search messages
tasks = requests.get(f"{API}/messages/search", params={"q": "TODO"}, headers=HEADERS).json()

# Reply
requests.post(f"{API}/actions/send/text", headers=HEADERS, json={
    "jid": "[email protected]",
    "text": "Got it! I'll handle that."
})

Node.js WebSocket listener

import WebSocket from "ws";

// With ticket auth (recommended — requires SECURITY_WS_TICKET_AUTH=true)
const res = await fetch("http://localhost:3100/api/ws/ticket", {
  method: "POST",
  headers: { "x-api-key": "YOUR_KEY" },
});
const { ticket } = await res.json();
const ws = new WebSocket(`ws://localhost:3100/ws?ticket=${ticket}&events=wa.messages.upsert`);

// Or with header auth (non-browser)
// const ws = new WebSocket("ws://localhost:3100/ws", { headers: { "x-api-key": "YOUR_KEY" } });

ws.on("message", (data) => {
  const event = JSON.parse(data);
  console.log("New message:", event.type, event.data);
});

Environment Variables

Core

Variable Default Description
PORT 3100 API server port
API_KEY (required) Authentication key (min 16 characters)
DATA_DIR ./data Data directory path
MEDIA_DIR ./data/media Media storage path
AUTO_DOWNLOAD_MEDIA true Auto-download media files
MAX_MEDIA_SIZE_MB 100 Max file size to download (0 = unlimited)
LOG_LEVEL info Pino log level
SESSION_NAME default Baileys auth session name
BEHIND_PROXY false Set true behind a TLS reverse proxy (enables HSTS, CSP upgrade-insecure-requests, trust proxy)
CORS_ORIGINS (auto) Allowed CORS origins (comma-separated, or *). Default: localhost + LAN IPs on configured port
WEBHOOK_URLS Comma-separated webhook URLs
WEBHOOK_SECRET HMAC secret for webhook signatures

Security

Migration notice: Starting with this version, SECURITY_WS_TICKET_AUTH and SECURITY_DISABLE_HTTP_QUERY_AUTH now default to ON (previously OFF). If you rely on query-string API key auth or raw WebSocket connections with ?api_key=, explicitly set SECURITY_WS_TICKET_AUTH=false and/or SECURITY_DISABLE_HTTP_QUERY_AUTH=false in your .env file or Docker Compose environment.

Variable Default Description
SECURITY_WS_TICKET_AUTH true Use one-time tickets for WebSocket auth instead of api_key in URL
SECURITY_DISABLE_HTTP_QUERY_AUTH true Disable ?api_key= query parameter on HTTP endpoints
SECURITY_ENCRYPT_DATABASE false Encrypt SQLite database at rest (requires ENCRYPTION_KEY)
SECURITY_ENCRYPT_WEBHOOK_SECRETS false Encrypt webhook HMAC secrets at rest (requires ENCRYPTION_KEY)
SECURITY_STRIP_RAW_MESSAGES false Omit raw_message field from API responses
ENCRYPTION_KEY Master encryption key (min 16 chars). Required by database and webhook secret encryption
SECURITY_AUTO_PRUNE false Auto-prune old presence and event log entries every 6 hours
PRESENCE_RETENTION_DAYS 7 Days to keep presence log entries (when auto-prune enabled)
EVENT_RETENTION_DAYS 30 Days to keep event log entries (when auto-prune enabled)
SECURITY_HASH_EVENT_JIDS false Hash phone numbers in event log for privacy (one-way)
SECURITY_SEC_FETCH_CHECK false Block cross-site browser requests via Sec-Fetch-Site header

Always-on hardening (no configuration needed): Bearer token parsing fix, WebSocket ping/pong heartbeat, input validation on group operations and order parameters, media URL fetch size limits + timeout, SSRF re-validation at webhook delivery, per-API-key rate limiting.

Reverse Proxy / HTTPS

WhatsApp Hub serves plain HTTP by default. If you place it behind a TLS-terminating reverse proxy (Caddy, nginx, Cloudflare Tunnel, etc.), set:

BEHIND_PROXY=true

This enables:

  • HSTS — tells browsers to always use HTTPS for this host
  • upgrade-insecure-requests in Content-Security-Policy — tells browsers to load sub-resources over HTTPS
  • trust proxy in Express — reads the real client IP from X-Forwarded-For

Leave it at false (the default) when accessing the app directly over HTTP, otherwise all asset loads will fail and you'll see a blank page.

Data Storage

data/
├── whatsapp-hub.db        # SQLite database (WAL mode, optionally encrypted)
├── auth/default/          # Baileys session credentials
└── media/
    └── 2025/01/15/        # Date-organized media files

When SECURITY_ENCRYPT_DATABASE=true, the database is encrypted at rest (AES-256). Existing unencrypted databases are automatically migrated on first start (a backup is created beforehand). No special build steps or system libraries are needed — encryption support is bundled.

Development

npm install
cd frontend && npm install && cd ..
cp .env.example .env
# Set your API_KEY in .env
npm run dev

The frontend dev server runs separately:

cd frontend
npm run dev

Tech Stack

Backend: Node.js, TypeScript, Express, Baileys, better-sqlite3 (with encryption), Pino

Frontend: React, TypeScript, Vite, Tailwind CSS, Radix UI, Zustand, TanStack Query, Recharts

License

MIT

About

A single self-hosted service that maintains a persistent WhatsApp connection and exposes everything through a clean REST API, real-time WebSocket stream, and webhook system.

Resources

License

Stars

Watchers

Forks

Contributors

Languages