WAHA: Self-Hosted WhatsApp HTTP API Explained
WAHA wraps unofficial WhatsApp Web engines in a Dockerized REST API you self-host. Here's how setup, sessions, sending, webhooks, engines, and tiers actually work.
- WAHA (WhatsApp HTTP API) by devlikeapro is an Apache-2.0, Dockerized REST wrapper that drives real WhatsApp Web sessions on your own server — not Meta's official Cloud API.
- It supports four engines via WHATSAPP_DEFAULT_ENGINE: WEBJS (browser/Chromium, default) and WPP (also browser-based) for maximum features, plus the browserless NOWEB (Node WebSocket) and GOWS (Golang WebSocket) for lighter, higher-scale deployments.
- A session is one WhatsApp number that moves through STARTING to SCAN_QR_CODE to WORKING; you authenticate by QR or pairing code and send via simple REST calls like POST /api/sendText.
- Core is free but limited to 1 session and text/non-media sends; Plus (~$19/mo) adds media and unlimited sessions; PRO (~$99/mo) adds source code and team seats.
- Because WAHA is an unofficial client, it can violate WhatsApp's Terms and risk bans — keep send rates low, warm up numbers, and consider the official Cloud API for compliant bulk messaging.
What WAHA actually is
WAHA stands for WhatsApp HTTP API. It is an open-source project maintained by devlikeapro (brand: devlike.pro) that packages an unofficial WhatsApp Web client behind a clean REST API and ships it as a Docker image. Instead of writing a Puppeteer or Baileys integration yourself, you run one container, scan a QR code, and start sending and receiving messages over HTTP.
The crucial detail to understand up front: WAHA logs in as a real WhatsApp account using the linked-device mechanism — the same way web.whatsapp.com pairs to your phone. It does NOT use Meta's official WhatsApp Business Cloud API. That distinction shapes everything else in this guide, from features to compliance risk. WAHA is not affiliated with or endorsed by WhatsApp or Meta.
- Repo: github.com/devlikeapro/waha (several thousand GitHub stars and actively maintained, with calendar-style versioning such as 2026.5.1; check the repo for the live star count).
- License: Apache-2.0 for WAHA Core; the Plus and PRO images are closed-source and paid.
- Images: devlikeapro/waha (free) and devlikeapro/waha-plus (paid, pulled with patron credentials).
- Docs: waha.devlike.pro, including a built-in Dashboard and Swagger/OpenAPI UI for QR scanning and session monitoring.
How the architecture fits together
Conceptually WAHA has three layers. At the bottom sits an engine that maintains the actual WhatsApp Web connection. On top of that is WAHA's normalization layer, which exposes a consistent REST surface and emits webhook events. Around both is the Docker container you self-host on any VPS or server, with no per-message cloud fees.
Because the engine does the real protocol work, your application code never touches WhatsApp directly. You make HTTP calls to your WAHA instance, and WAHA receives inbound traffic and forwards it to your webhook endpoint. This is what makes WAHA approachable: the hard parts (browser automation or socket protocol) are hidden behind ordinary JSON over HTTP.
If you want broader context on the libraries underneath these engines, see our overviews of the WhatsApp multi-device protocol and the best open-source WhatsApp libraries.
The four engines: WEBJS, WPP, NOWEB, GOWS
WAHA lets you choose an engine with the WHATSAPP_DEFAULT_ENGINE environment variable. The official engines documentation lists four options that trade off maturity, feature coverage, and resource cost. Two are browser-based (WEBJS and WPP) and two are browserless WebSocket engines (NOWEB and GOWS). WEBJS is the default.
| Engine | How it connects | Resource cost | Best for |
|---|---|---|---|
| WEBJS (default) | Real Chromium/Chrome browser via Puppeteer (whatsapp-web.js) | Heavy CPU/RAM | Maximum feature coverage and stability; the most battle-tested choice |
| WPP | Real Chromium/Chrome browser via Puppeteer (WPPConnect) | Heavy CPU/RAM | An alternative browser engine if a feature works better in WPPConnect than whatsapp-web.js |
| NOWEB | Direct WebSocket in Node.js/TypeScript (Baileys-based), no browser | Light | Running more sessions per server on modest hardware |
| GOWS | Direct WebSocket in Golang (whatsmeow-based), no browser | Light, high-scale | High concurrency; the newest engine and positioned as NOWEB's future replacement |
The browser-based engines (WEBJS and WPP) run an actual Chromium instance, which is why they tend to support the widest feature set (including things like screenshots) — at the cost of significant memory per session. NOWEB and GOWS skip Chromium entirely and talk to WhatsApp over a WebSocket, so they are dramatically lighter and scale much further per server. GOWS is the newest engine; the docs describe it as a future replacement for NOWEB. Concrete per-server session counts depend heavily on your hardware, the engine, and your message volume, so treat any single number you see quoted as a rough community figure rather than a guarantee — benchmark on your own infrastructure.
For deeper background on the engine internals, our guides on Baileys, whatsapp-web.js, and whatsmeow (the Go library) explain the trade-offs that surface here as NOWEB, WEBJS, and GOWS.
Setup: one container to get running
The fastest start is a single docker run. The example below launches WAHA Core, exposes the dashboard and API on port 3000, and selects the default WEBJS engine explicitly so the configuration is obvious.
# Run WAHA Core (free, Apache-2.0)
docker run -it --rm \
-p 3000:3000 \
-e WHATSAPP_DEFAULT_ENGINE=WEBJS \
--name waha \
devlikeapro/waha
# Open the dashboard / Swagger UI at:
# http://localhost:3000Once it is up, visit the dashboard, start a session, and scan the QR code with WhatsApp on your phone (Linked Devices). For a more durable deployment, use Docker Compose and mount a volume so authentication state survives restarts. Note that managed storage backends (PostgreSQL/MongoDB and S3) are a Plus feature; Core uses local file storage.
# docker-compose.yml
services:
waha:
image: devlikeapro/waha
restart: always
ports:
- "3000:3000"
environment:
WHATSAPP_DEFAULT_ENGINE: WEBJS
WHATSAPP_HOOK_URL: "https://your-app.example.com/webhook"
WHATSAPP_HOOK_EVENTS: "message,session.status"
WHATSAPP_HOOK_HMAC_KEY: "replace-with-a-strong-secret"
volumes:
# Core persists session/auth state to /app/.sessions by default
# (override with WAHA_LOCAL_STORE_BASE_DIR).
- ./waha-sessions:/app/.sessionsSessions and authentication
In WAHA a session represents one WhatsApp account (one number). Each session moves through a clear lifecycle: STOPPED to STARTING to SCAN_QR_CODE to WORKING, with FAILED as the error state. You manage sessions entirely over REST.
| Action | Endpoint |
|---|---|
| Create a session | POST /api/sessions |
| List sessions | GET /api/sessions |
| Start / Stop / Restart | POST /api/sessions/{name}/start | /stop | /restart |
| Logout (clear auth) | POST /api/sessions/{name}/logout |
| Delete session | DELETE /api/sessions/{name} |
Authentication is done either by QR code or by pairing code. With QR, you fetch GET /api/{session}/auth/qr — the first code lasts about 60 seconds and subsequent ones about 20 seconds each. The pairing-code flow (POST /api/{session}/auth/request-code with a phone number) returns a code you type into WhatsApp; the docs recommend treating it as a fallback. If you are weighing the two methods, see our comparison of QR vs pairing code.
Session state persists across container restarts, so you do not re-scan every time. In WAHA Plus, the WAHA_WORKER_RESTART_SESSIONS behavior autostarts sessions and you can back state with PostgreSQL/MongoDB or S3 for a more resilient setup.
Sending messages and receiving webhooks
Sending is a plain REST call. The text endpoint is POST /api/sendText, and there are sibling endpoints for contacts and location alongside media. Per the send-messages docs, text, location, and contact (vCard) sends are available in Core, while media sends — image, file, voice, and video — require Plus. Here is a minimal text send in Node.js.
// Send a text message via WAHA
const res = await fetch("http://localhost:3000/api/sendText", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
session: "default",
chatId: "[email protected]", // <country-code><number>@c.us
text: "Hello from WAHA!"
})
});
console.log(await res.json());Inbound traffic arrives via webhooks, configured either per-session (in the session's config.webhooks[] array) or globally with environment variables such as WHATSAPP_HOOK_URL, WHATSAPP_HOOK_EVENTS, and WHATSAPP_HOOK_HMAC_KEY. Useful events include message, message.any, message.ack, message.reaction, session.status, group.v2.* , presence.update, and call.*.
Webhooks are signed for integrity: WAHA computes an HMAC SHA-512 over the raw request body and sends it in the X-Webhook-Hmac header (with X-Webhook-Hmac-Algorithm naming the algorithm). Verify it on your side before trusting any payload, and use a constant-time comparison so an attacker cannot learn the correct signature byte-by-byte through timing.
// Verify a WAHA webhook (Express + HMAC SHA-512)
import crypto from "node:crypto";
app.post("/webhook", express.raw({ type: "*/*" }), (req, res) => {
const signature = req.header("X-Webhook-Hmac") || "";
const expected = crypto
.createHmac("sha512", process.env.WHATSAPP_HOOK_HMAC_KEY)
.update(req.body) // raw Buffer, not parsed JSON
.digest("hex");
// Constant-time comparison to avoid timing attacks.
const sigBuf = Buffer.from(signature, "hex");
const expBuf = Buffer.from(expected, "hex");
const ok =
sigBuf.length === expBuf.length &&
crypto.timingSafeEqual(sigBuf, expBuf);
if (!ok) return res.sendStatus(401);
const event = JSON.parse(req.body.toString());
console.log(event.event, event.payload);
res.sendStatus(200);
});Delivery is resilient too: retry policy is configurable as constant, linear, or exponential backoff, with adjustable attempt counts and delays. For patterns that apply to any provider, our WhatsApp webhooks guide goes deeper on idempotency and signature handling.
Core vs Plus vs PRO
WAHA's tiers gate two things above all: media support and the number of concurrent sessions. The free Core tier is genuinely usable for text bots, but it is single-session and cannot send media. Prices below were verified on the official support-us page in 2026 and the maintainer treats this as a patron-style subscription, so confirm current figures at waha.devlike.pro/support-us before budgeting.
| Capability | Core (Free) | Plus (~$19/mo) | PRO (~$99/mo) |
|---|---|---|---|
| Sessions | 1 | Unlimited | Unlimited |
| Text / location / contact send | Yes | Yes | Yes |
| Media (image/voice/video/file) | No | Yes | Yes |
| Engines (WEBJS/WPP/NOWEB/GOWS) | All four | All four | All four |
| Webhooks | Yes | Yes | Yes |
| Storage backends (PostgreSQL/MongoDB, S3) | File storage only | Yes | Yes |
| Source code access | Core only (Apache-2.0) | No | Yes (Plus source) |
| Team seats / priority support | No | Discord community | Up to 5 seats + priority |
A practical note on the licensing model: the maintainer states there are no online license checks or expiration on an already-installed instance — but pulling the waha-plus or PRO image requires an active subscription. The biggest free-tier gotcha is media: do not assume Core can send images or voice notes, because it cannot. Text, location, and contact cards do work on Core.
Compliance, ban risk, and the official alternative
WAHA is powerful precisely because it acts as a real WhatsApp client — and that is also its risk. Meta's Terms prohibit unofficial and automated clients, so any WAHA deployment carries the possibility of account bans. The danger spikes with high-volume or fast sending, brand-new numbers, and unsolicited messages.
- Keep send rates conservative. WhatsApp publishes no official rate limit for linked devices, so any specific number is an unofficial community heuristic rather than a safe threshold — staying well below a few dozen messages per minute on a fresh number is a common rule of thumb, not a guarantee against a ban.
- Warm up numbers gradually rather than blasting from day one.
- Only message users who have opted in, and make stopping easy.
- Use a dedicated number you can afford to lose, never a critical personal/business line.
- Monitor session.status webhooks so you detect a logout or ban quickly.
For regulated or large-scale commercial messaging, the official WhatsApp Business Cloud API is the Terms-compliant path: it is built for automation, supports verified senders and message templates, but charges per conversation and requires Business verification. The honest trade-off is cost and approval friction versus compliance and stability. Our comparison of the Cloud API vs unofficial clients and the tips in how to avoid a WhatsApp ban lay this out in detail.
If your goal is simply to verify whether numbers are on WhatsApp or to fetch public profile data — rather than two-way messaging — you may not need to run an unofficial client at all. A hosted lookup API can return on-WhatsApp status, public profile picture, name, about, and business-account detection without you managing sessions or QR codes.
Frequently asked questions
Skip running an unofficial WhatsApp session. Our hosted WhatsApp Profile API tells you whether a number is on WhatsApp and returns its public picture, name, about, and business status — no Docker, no QR codes, no ban risk to manage.
Explore the API