OpenWA Logo

OpenWA

Open Source WhatsApp API Gateway

FeaturesQuick StartDocsAPIContributing

CI Version License Node NestJS Docker TypeScript

--- ## ✨ Why OpenWA? **OpenWA** is a free, open-source WhatsApp API Gateway designed for developers who need full control over their messaging infrastructure—without vendor lock-in or hidden paywalls. Built on a **pluggable architecture**, OpenWA lets you select database engines (SQLite/PostgreSQL), backup/migration storage backends (Local/S3), and cache layers (disabled/Redis) through configuration rather than application-code changes. Message media itself is returned inline to API and webhook consumers; it is not automatically persisted to the storage backend. | | | | ----------------------------- | ------------------------------------------------------------------------------------------ | | 🔓 **100% Open Source** | No licensing fees, no feature locks, full source code access | | 🏗️ **Pluggable Architecture** | Swap adapters for database, storage, and cache via config | | 🖥️ **Full Dashboard** | Modern React UI for session, webhook, and API key management | | 🔹 **Multi-Session Ready** | Run multiple WhatsApp sessions concurrently on one instance | | 🐳 **Docker Native** | Production-ready with zero configuration | | 🧩 **Official Plugins** | Chatwoot, Typebot & more as sandboxed plugins on the Integration Fabric — [OpenWA-plugins](https://github.com/rmyndharis/OpenWA-plugins) | | 🔗 **n8n Integration** | Community nodes for workflow automation | | 🧩 **Community Adapters** | Third-party integrations (e.g. ioBroker) — see [docs](./docs/23-community-integrations.md) | --- ## ⚠️ Before you connect a number — please read OpenWA is an unofficial, community-maintained gateway. It connects to WhatsApp through **reverse-engineered clients** (the [`whatsapp-web.js`](https://github.com/pedroslopez/whatsapp-web.js) project and [`@whiskeysockets/baileys`](https://github.com/WhiskeySockets/Baileys)), **not** through Meta's official Cloud API. This has real consequences you should understand before you link a phone number. ### What this means in practice - **There is always a non-zero risk of account restriction or ban.** WhatsApp's anti-abuse systems actively look for unofficial automation. No amount of code quality on our side can make that risk zero. - **Pick the right number.** Never connect your primary personal or business number to an automated gateway. Use a **dedicated number** you can afford to lose. If you're running this for paying clients, pass that guidance on to them. - **The two engines trade off differently:** | Engine | Ban-risk profile | Resource cost | | ----------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------- | | `whatsapp-web.js` | Lower — drives a real headless Chromium that looks like genuine WhatsApp Web traffic. | High RAM (~300–500 MB / session). | | `baileys` | Higher — speaks the multi-device WebSocket protocol directly and is easier for WhatsApp to fingerprint. | Low RAM (~30–80 MB / session). | If account safety is your top priority and you can afford the memory, prefer `whatsapp-web.js`. If you need density and accept the trade-off, use `baileys`. ### Safe-sending guidelines These are practical guardrails, not guarantees — but they materially reduce the chance of WhatsApp flagging the account: 1. **Warm up fresh numbers.** For the first several days, behave like a normal human user: scan the QR, exchange a handful of messages with saved contacts, join a group or two, set a profile photo. Don't blast on day one. 2. **Don't cold-blast strangers.** Sending the first-ever message to a large batch of numbers that have never messaged you is the single most reliable way to get restricted — on either engine. 3. **Rate-limit yourself.** OpenWA ships with a configurable rate limiter (`RATE_LIMIT_*` env vars). Use it. A few messages per minute per session is sustainable; "thousands in an hour" is not. 4. **Use opted-in recipients.** The safest workloads are replies and alerts to people who already expect to hear from you (OTP to your own users, order updates, support replies). 5. **Keep a fallback.** For anything auth-critical or revenue-critical, keep an SMS / email / official-Cloud-API path. Do not bet a login flow solely on an unofficial client. 6. **Mind the hosting IP.** Cheap datacenter IPs are flagged more aggressively than residential ones. A residential proxy (supported per-session via the proxy settings) can help; it is not a license to spam. ### Known platform behaviour (not bugs) A few things that look like bugs but are actually server-side WhatsApp policy, not OpenWA defects — we track them separately so we can distinguish them from real bugs: - **First message to a brand-new contact sometimes never arrives.** The API returns success because the message leaves OpenWA, but WhatsApp's server-side reach-out / trust policy drops it at delivery. This is independent of OpenWA. We track it in [#830](https://github.com/rmyndharis/OpenWA/issues/830). - **Accounts that get restricted cannot be "unrestricted" by us.** If WhatsApp disables a number, you need to appeal through their channels — OpenWA has no lever to pull. ### Compliance For any deployment where ethical, legal, or regulatory compliance matters (healthcare, finance, large-scale commercial messaging, anything touching end users in the EU/EEA under DMA/GDPR framings), treat OpenWA as **not approved** and use Meta's [official WhatsApp Cloud API](https://developers.facebook.com/docs/whatsapp/cloud-api). OpenWA is an excellent fit for personal projects, internal tooling, automation hobbyists, and learning — it is not a drop-in replacement for the official API in regulated environments. 📖 For the deeper, maintainer-side risk analysis (protocol-change exposure, dependency strategy, security posture), see [Risk Management (`docs/16`)](./docs/16-risk-management.md). --- ## 🎯 Features ### Core Features | Feature | Status | Description | | ------------- | ------ | ---------------------------------------------------------------------------- | | REST API | ✅ | Full WhatsApp API via HTTP endpoints | | Multi-Session | ✅ | Manage multiple WhatsApp accounts | | Webhooks | ✅ | Real-time events with HMAC signature and optional smart pre-dispatch filters | | Web Dashboard | ✅ | Visual management interface | | API Key Auth | ✅ | Secure API authentication | | Swagger Docs | ✅ | Interactive API documentation | ### Messaging | Feature | Status | Description | | ----------------- | ------ | -------------------------------------------- | | Text Messages | ✅ | Send/receive text messages | | Media Messages | ✅ | Images, videos, documents, audio | | Message Reactions | ✅ | React to messages with emoji | | Message Editing | ✅ | Send edits + live `message.edited` events on both engines | | Bulk Messaging | ✅ | Send to multiple recipients | | Message Status | ✅ | Track delivery and read receipts | ### Advanced | Feature | Status | Description | | ------------------- | ------ | ---------------------------------- | | Groups API | ✅ | Create, manage, join (invite code), and configure groups | | Profile Management | ✅ | Set own display name, about text, and profile picture | | Call Handling | ✅ | `call.received` events, reject calls, per-session auto-reject | | Channels/Newsletter | ✅ | WhatsApp Channels support | | Labels Management | ✅ | Organize chats with labels | | Proxy Support | ✅ | Per-session proxy configuration | | Rate Limiting | ✅ | Configurable request limits | | CIDR Whitelisting | ✅ | IP-based access control | | Audit Logging | ✅ | Audit trail for API-key, session, integration-instance, and infra admin operations (message sends and webhook deliveries are tracked in their own tables, not the audit log) | ### Infrastructure | Feature | Status | Description | | ---------------- | ------ | ---------------------------------------- | | SQLite | ✅ | Zero-config embedded database | | PostgreSQL | ✅ | Production-grade database | | Redis Cache | ✅ | Optional performance caching | | S3/MinIO Storage | ✅ | Media-directory backup/migration backend | | Docker | ✅ | One-command deployment | | Health Checks | ✅ | Kubernetes-ready probes | | Data Migration | ✅ | Export/import between backends | --- ## 🚀 Quick Start ### Option A: Docker (Recommended) ```bash # Clone and start git clone https://github.com/rmyndharis/OpenWA.git cd OpenWA docker compose -f docker-compose.dev.yml up -d # Access (the dashboard is bundled into the API image and served on the same port) # Dashboard: http://localhost:2785 # API: http://localhost:2785/api # Swagger: http://localhost:2785/api/docs ``` > **Using Podman instead of Docker?** > Podman rootless mode requires the socket to be running and `DOCKER_HOST` to be set: > > ```bash > systemctl --user start podman.socket > systemctl --user enable podman.socket > export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock > ``` > > Add the `export` line to your `~/.bashrc` to make it permanent. ### Option B: Local Development ```bash # Clone repository git clone https://github.com/rmyndharis/OpenWA.git cd OpenWA # Install the locked dependencies (includes dashboard) npm ci # Start API + Dashboard (config is auto-generated on first run) npm run dev # Access (in dev the dashboard runs on the Vite server with hot reload) # Dashboard: http://localhost:2886 # API: http://localhost:2785/api # Swagger: http://localhost:2785/api/docs ``` Use `npm install` instead when intentionally changing dependencies. OpenWA's committed lockfile uses registry artifacts only, so npm 12 works with its secure default that blocks Git dependencies; do not disable that policy globally. --- ## 🔒 Security Architecture ### Docker Socket Proxy The production stack never exposes `/var/run/docker.sock` directly to the application container. Instead, a dedicated `docker-proxy` sidecar (based on [`tecnativa/docker-socket-proxy`](https://github.com/Tecnativa/docker-socket-proxy)) acts as the sole gateway to the Docker daemon: ``` openwa-api ──TCP 2375──▶ docker-proxy ──unix──▶ /var/run/docker.sock ``` Only the operations needed for container orchestration are enabled (`CONTAINERS`, `IMAGES`, `VOLUMES`, `INFO`, `PING`, plus the `POST` method switch). The application connects via the `DOCKER_HOST=tcp://docker-proxy:2375` environment variable, which `DockerService` detects automatically. Note this is an operational gateway, not a fine-grained privilege boundary: with `POST` enabled the proxy admits every method to the enabled paths and cannot scope container-create payloads, so a compromised API container would be host-root-equivalent — see `SECURITY.md` for the full threat model, mitigations, and how to disable the proxy if you don't use the built-in datastore orchestration. ### Non-root Container Execution The production image never runs the Node.js process as root. On startup, the container follows this chain: ``` dumb-init (PID 1) └─ docker-entrypoint.sh (root — fixes named-volume ownership via chown) └─ gosu openwa node dist/main (drops to the openwa user) ``` - **dumb-init** is PID 1 and forwards signals (SIGTERM, etc.) for graceful shutdown. - **docker-entrypoint.sh** runs as root only long enough to `chown` the named-volume mount points so the `openwa` user can write to them. - **gosu** performs a clean `exec`-based privilege drop — no `su` or `sudo` wrappers, so the node process is the direct child of dumb-init. Named volumes (e.g. `openwa-data`) get their ownership corrected automatically on every start, so no manual `chown` step is needed after volume creation. --- ## 🏭 Production Deployment For production, use the main `docker-compose.yml` with optional services: ```bash # Basic production (SQLite, local storage) docker compose up -d # With PostgreSQL database docker compose --profile postgres up -d # Full stack (PostgreSQL, Redis, MinIO) docker compose --profile full up -d ``` | Profile | Services | | ---------- | --------------------- | | `postgres` | PostgreSQL database | | `redis` | Redis cache | | `minio` | S3-compatible storage | | `full` | All services above | > The dashboard is bundled into the API image and served by NestJS on the API port, so it > needs no profile — it is always available wherever `openwa-api` runs. For TLS/public exposure, > put your own reverse proxy (nginx, Caddy, a cloud load balancer, or a k8s Ingress) in front; > see the nginx example in `docs/12-troubleshooting-faq.md`. > **Development vs Production** > > - Development (`docker-compose.dev.yml`): SQLite, local storage, API serves the bundled dashboard > - Production (`docker-compose.yml`): Configurable database, profiles for optional services > > Official GHCR images are published as multi-arch manifests for: > > - `linux/amd64` > - `linux/arm64` ## 🔌 Ports | Service | Port | Description | | --------------- | --------------- | ----------------------------------------------- | | API & Dashboard | `2785` | REST API + bundled web dashboard (same port) | | Swagger | `2785/api/docs` | Interactive API docs | | Dashboard (dev) | `2886` | Vite dev server with hot reload (`npm run dev`) | --- ## 📡 API Examples ### Create a Session ```bash curl -X POST http://localhost:2785/api/sessions \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY" \ -d '{"name": "my-bot"}' ``` ### Start Session & Get QR Code ```bash # Start the session curl -X POST http://localhost:2785/api/sessions/{sessionId}/start \ -H "X-API-Key: YOUR_API_KEY" # Get QR code (scan with WhatsApp) curl http://localhost:2785/api/sessions/{sessionId}/qr \ -H "X-API-Key: YOUR_API_KEY" ``` ### Send a Message ```bash curl -X POST http://localhost:2785/api/sessions/{sessionId}/messages/send-text \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY" \ -d '{ "chatId": "628123456789@c.us", "text": "Hello from OpenWA!" }' ``` ### Setup Webhook ```bash curl -X POST http://localhost:2785/api/sessions/{sessionId}/webhooks \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY" \ -d '{ "url": "https://your-server.com/webhook", "events": ["message.received", "session.status"], "secret": "your-hmac-secret" }' ``` > **Smart filters (optional):** add a `filters` object to fire the webhook only when conditions match > (AND), e.g. `{ "conditions": [{ "field": "sender", "operator": "is", "value": ["1234567890@c.us"] }] }`. > Fields: `sender` / `recipient` / `body` / `type` / `mentions` / `fromMe` / `hasMedia` / `isGroup`. A > webhook with no filters behaves exactly as before. See the API specification for the full schema. ## 🤖 MCP Server (AI Agents) OpenWA can expose a **curated set of tools over the [Model Context Protocol](https://modelcontextprotocol.io)** so AI agents (Claude, Cursor, …) can drive WhatsApp. It is **off by default** and **additive** — every REST route keeps working unchanged. Set `MCP_ENABLED=true` to mount a stateless Streamable-HTTP transport at **`POST /mcp`** on the existing server (same port, no extra process). It exposes ~39 curated tools (sessions, messaging, contacts, basic group ops, webhook reads) — a focused surface rather than the full API, so agents aren't overwhelmed and destructive operations stay off the agent path. ```bash MCP_ENABLED=true npm run start:prod # or set MCP_ENABLED in your .env / compose ``` Point an MCP client at it (e.g. for Claude Code, a `.mcp.json` at your project root): ```json { "mcpServers": { "openwa": { "type": "http", "url": "http://localhost:2785/mcp", "headers": { "Authorization": "Bearer YOUR_API_KEY" } } } } ``` The key can be passed as `Authorization: Bearer …` or `X-API-Key: …`. Every tool call goes through the **same API-key auth, role, and per-session scoping** as REST. **Security guidance:** - **Mint a dedicated, least-privilege key** for the agent — a non-admin, **session-scoped** key (`OPERATOR` role at most). The plaintext key is shown only once on creation; to rotate, create a new key and delete the old one. - The key **must not** carry an IP allow-list (`allowedIps`) — there is no genuine client IP over MCP, so such a key is rejected. - Set **`MCP_READONLY=true`** to mount only the read tools (no sends/writes). - Set **`MCP_RATE_LIMIT_MAX`** (default `60`) to limit tool calls per API key per window. - Set **`MCP_RATE_LIMIT_WINDOW_MS`** (default `60000`) to control the sliding window size in milliseconds. - **Do not expose `/mcp` to the public internet** without a fronting auth proxy. For a self-hosted, locally-reached deployment the static API key is appropriate; public exposure should use OAuth 2.1 (not yet built). --- ## 🛠 Tech Stack | Layer | Technology | | ------------- | ------------------------------------------------------- | | **Runtime** | Node.js 22 LTS | | **Framework** | NestJS 11.x | | **Language** | TypeScript 6.x | | **WA Engine** | whatsapp-web.js (default) / baileys — set `ENGINE_TYPE` | | **Database** | SQLite / PostgreSQL | | **Cache** | Redis (optional) | | **Storage** | Local / S3 / MinIO | | **ORM** | TypeORM | | **Container** | Docker + Docker Compose | --- ## 📁 Project Structure ``` openwa/ ├── src/ │ ├── main.ts # Application entry point │ ├── app.module.ts # Root module │ ├── config/ # Configuration │ ├── common/ # Shared utilities │ │ ├── cache/ # Redis caching │ │ └── storage/ # File storage (Local/S3) │ ├── core/ # Core systems │ │ ├── hooks/ # Plugin hooks │ │ └── plugins/ # Plugin system │ ├── engine/ # WhatsApp engine abstraction │ └── modules/ │ ├── session/ # Session management │ ├── message/ # Message handling │ ├── webhook/ # Webhook management │ ├── group/ # Groups API │ ├── contact/ # Contacts API │ ├── auth/ # API key authentication │ ├── infra/ # Infrastructure management │ └── health/ # Health checks ├── dashboard/ # React web dashboard ├── docs/ # Documentation ├── docker-compose.yml ├── Dockerfile └── package.json ``` --- ## 📚 Documentation Comprehensive documentation is available in the `docs/` folder: | Document | Description | | ------------------------------------------------------- | ---------------------------- | | [Project Overview](./docs/01-project-overview.md) | Introduction and goals | | [Requirements](./docs/02-requirements-specification.md) | Feature specifications | | [Architecture](./docs/03-system-architecture.md) | System design | | [Security](./docs/04-security-design.md) | Security implementation | | [Database](./docs/05-database-design.md) | Data models and migrations | | [API Spec](./docs/06-api-specification.md) | Complete API reference | | [Development](./docs/08-development-guidelines.md) | Coding standards | | [Migration Guide](./docs/14-migration-guide.md) | Database & storage migration | --- ## 🤝 Contributing We welcome contributions! Here's how to get started: 1. **Fork** the repository 2. **Create** your feature branch (`git checkout -b feature/amazing-feature`) 3. **Commit** your changes (`git commit -m 'Add amazing feature'`) 4. **Push** to the branch (`git push origin feature/amazing-feature`) 5. **Open** a Pull Request Please read our [Development Guidelines](./docs/08-development-guidelines.md) for coding standards and best practices. --- ## 📄 License This project is licensed under the **MIT License** – free for personal and commercial use. See [LICENSE](./LICENSE) for details. ---
**OpenWA** – Free, Open Source WhatsApp API Gateway [📖 Documentation](./docs/README.md) · [🔌 API Docs](http://localhost:2785/api/docs) · [🐛 Report Bug](https://github.com/rmyndharis/OpenWA/issues) · [💡 Request Feature](https://github.com/rmyndharis/OpenWA/issues)
Made with ❤️ by Yudhi Armyndharis and the OpenWA Community