Self-hosted service
rmyndharis/OpenWA avatar
rmyndharis/OpenWA

OpenWA: a self-hosted WhatsApp gateway that runs on unofficial clients

OpenWA is a self-hosted WhatsApp API gateway for applications that need to send messages and manage sessions on their own server.

14,760 stars3,450 forksTypeScriptMIT

At a glance

What is it?
OpenWA is an MIT-licensed TypeScript gateway that exposes WhatsApp sessions over an HTTP API, with a dashboard, Docker Compose, and two interchangeable engines. The catch is written into its own README: it drives reverse-engineered clients, not Meta's Cloud API.
Who is it for?
Adopt OpenWA for internal tooling, personal projects, and replies or alerts to people who already expect to hear from you, on a dedicated number you can afford to lose. Do not adopt it for anything the README calls compliance-sensitive, including healthcare, finance, large-scale commercial messaging, and EU/EEA deployments under DMA or GDPR framings; the project itself says to treat it as not approved there and use Meta's official WhatsApp Cloud API.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What OpenWA actually is, and who it is for

OpenWA is a self-hosted HTTP API in front of WhatsApp. Applications send messages and manage sessions through it, on their own server, instead of through a hosted messaging vendor. The primary language is TypeScript, the licence is MIT, and the default branch is main. The repository ships a dashboard, an SDK directory, an openapi.json, Helm charts, and a Docker Compose file, so the intended shape is a service you run rather than a library you import.

The audience is narrow and the README says so. It is for developers who want control over messaging infrastructure without vendor lock-in or paywalls, and the project positions itself for personal projects and internal tooling. A separate integration repository hosts official plugins for Chatwoot and Typebot, and the README points at community nodes for n8n and third-party adapters such as ioBroker. If your use case is a workflow that reacts to incoming messages, those integrations are the reason to look at this project rather than at a raw client library.

The part that decides whether OpenWA is even a candidate sits in a warning above the feature list. It connects through reverse-engineered clients, whatsapp-web.js and @whiskeysockets/baileys, not through Meta's official Cloud API. That single sentence changes the risk profile of everything built on top.

Two engines, and the ban-risk trade-off you cannot configure away

OpenWA does not have one WhatsApp client. It has two, and the choice between them is a memory-versus-risk decision the README tabulates directly.

whatsapp-web.js drives a real headless Chromium, so the traffic looks like genuine WhatsApp Web. The README puts its resource cost at roughly 300 to 500 MB of RAM per session and calls its ban-risk profile lower. baileys speaks the multi-device WebSocket protocol directly, costs roughly 30 to 80 MB per session, and carries a higher ban-risk profile because it is easier for WhatsApp to fingerprint. If account safety outranks density, the README's own recommendation is whatsapp-web.js. If you need many sessions on one box and accept the trade-off, baileys is the dense option.

This is the most honest part of the documentation and also the most limiting. You are choosing which way to be exposed, not whether. The README states plainly that there is always a non-zero risk of account restriction or ban, and that no amount of code quality on the project's side makes that risk zero. It also separates platform behaviour from defects: the first message to a brand-new contact sometimes never arrives, the API returns success because the message left OpenWA, and WhatsApp's server-side reach-out policy drops it at delivery. The project tracks that under issue #830 rather than treating it as a bug. If your application treats a 200 response as proof of delivery, that gap will bite you.

Pluggable adapters, and where media actually goes

The architecture is described as pluggable, and the README is specific about the axes: database engine (SQLite or PostgreSQL), backup and migration storage backend (local or S3), and cache layer (disabled or Redis). The point of the design is that these are configuration choices rather than application-code changes, which matters if you start on SQLite for a single instance and later move to PostgreSQL and Redis without forking anything.

One detail in that table is easy to misread on a first pass. Message media is returned inline to API and webhook consumers; it is not automatically persisted to the storage backend. The storage adapters exist for backup and migration, not as a media archive. If you assume that pointing OpenWA at S3 means every incoming image is retained there, you will build on a false premise. Retention is your job, and the README's phrasing is the evidence for that.

The Docker Compose file shows a second architectural decision that is less about adapters and more about blast radius. A docker-proxy service (tecnativa/docker-socket-proxy) is the sole container with access to /var/run/docker.sock, mounted read-only, and it sits on an isolated internal network reachable only by the API container, not by the dashboard. The comment in the compose file is candid about the residual hole: the pinned proxy version cannot scope container-create payloads, so a compromised API container could create containers with host bind-mounts. The same comment notes that the proxy's DELETE environment flag is dead config in that pinned version and that OpenWA itself never issues deletes, since profile teardown is stop-only. The documented mitigation is to disable the docker-proxy service entirely if you do not use the built-in datastore orchestration toggles.

Installing OpenWA with Docker Compose and sending a first message

The README's quick-start path is Docker, and the repository ships docker-compose.yml and docker-compose.dev.yml alongside a multi-stage Dockerfile. The Dockerfile pins its builder to the build host's platform because it only produces architecture-independent artifacts, and it pins the base image digest for node:22-slim. The package.json engine field requires Node 22.13 or newer for a bare-metal install, so the container route avoids that question entirely.

Configuration is the part worth reading before you start. The .env.example is the single source of truth, and it explains a three-layer precedence order: process environment beats .env, which beats data/.env.generated, which is what the dashboard writes. The file deliberately ships dashboard-owned settings commented out, because an uncommented value becomes a pin: the dashboard control still moves and saves while the running value never changes. An empty assignment pins just as hard as a filled one. A spec file, src/config/env-precedence.spec.ts, fails if a dashboard-owned key is shipped uncommented again, which tells you the maintainers treat this as a real footgun rather than a style note.

Start from the example file and leave the dashboard-owned keys commented:

bash
cp .env.example .env

In the bundled Compose setup the container always listens on 2785; the PORT value of 2785 in .env applies when you run the app directly, and the host-published port is a separate setting. Bring the stack up with the repository's compose file:

bash
docker compose up -d

Once the API is running, the dashboard is where you scan a session QR code, register webhooks, and issue API keys. The README describes the dashboard as a React UI for session, webhook, and API key management, so the first real use is: create a session, link a number by scanning the QR, create an API key, then call the API. The repository contains an SDK directory and an exported openapi.json, and package.json exposes scripts for exporting and checking that spec, so the endpoint list is generated from the code rather than maintained by hand. Read openapi.json for the exact route and payload for message sending in your version; the README's API examples section is the other reference point. Before you send anything at volume, set the rate limiter through the RATE_LIMIT_* environment variables mentioned in the safe-sending guidelines.

Where OpenWA is the wrong tool

The compliance paragraph is the clearest limitation in the repository, and it is written by the maintainers about their own project. For any deployment where ethical, legal, or regulatory compliance matters, including healthcare, finance, large-scale commercial messaging, and anything touching end users in the EU/EEA under DMA or GDPR framings, the README says to treat OpenWA as not approved and to use Meta's official WhatsApp Cloud API instead.

That is not a hedge, it is a scope boundary. The gateway's own guidance adds that accounts which get restricted cannot be unrestricted by the project; appeals go through WhatsApp's channels, and OpenWA has no lever. Combined with the note that the first message to a brand-new contact can be silently dropped server-side, the practical consequence is that any flow where a missed message breaks something important, such as a login or a payment confirmation, should keep an SMS, email, or official Cloud API path alongside it. The README states exactly that.

There is a second, quieter failure mode in the configuration design. Because a value present in .env wins over everything the dashboard saves, an operator who copies .env.example and uncomments a setting can spend an afternoon debugging a dashboard toggle that reports success and does nothing. The precedence rule is documented, but it is the kind of trap that only reveals itself after the mistake.

The hosting IP is another constraint the README raises. Cheap datacenter IPs are flagged more aggressively than residential ones, and a residential proxy is supported per session through the proxy settings. The project is explicit that this is not a licence to spam, and it is worth reading that sentence as a boundary rather than a feature.

How OpenWA differs from WAHA and from the official Cloud API

The obvious comparison is WAHA, another self-hosted WhatsApp HTTP API. Both wrap unofficial clients and both expose sessions over HTTP, so the difference is not the protocol layer. It is the packaging philosophy. OpenWA's distinguishing claim is a pluggable adapter set for database, storage, and cache selected through configuration, plus a bundled dashboard, an exported OpenAPI spec, an SDK directory, and Helm charts in the same repository. The honest way to compare them is to check which storage and cache backends each supports, whether the OpenAPI spec is generated from the code, and how each handles the engine choice between a Chromium-driven client and a WebSocket client. Those are the questions that decide a deployment, and the OpenWA README answers them for OpenWA only.

The other comparison is Meta's official WhatsApp Cloud API, and here the difference is categorical rather than incremental. The Cloud API is sanctioned, so the account-restriction risk described above does not apply in the same way, and it is the destination the README names for compliance-sensitive work. What you give up is self-hosting: you are back on a hosted service with its own terms, pricing, and policy surface, which is precisely the vendor relationship OpenWA exists to avoid. Choosing between them is choosing which set of constraints you can live with, not which product is better. For internal alerts and personal automation on a spare number, OpenWA's constraints are tolerable. For a regulated flow, they are not, and the project says so first.

Maintenance, upgrades, and what the MIT licence does not cover

The last push to the default branch was on 2026-08-24, the same day as the v0.23.3 release, and the two preceding releases, v0.23.2 and v0.23.1, landed on 2026-08-23 and 2026-08-21. That is a tight release cadence in the days before the last push, and the repository is not archived. It does not follow that the project is actively maintained today; the only fact available is the date of that last push.

The upgrade surface is wider than a single binary. The Dockerfile pins its builder base image by digest and the compose file pins the docker-socket-proxy image at v0.4.2, so image bumps are deliberate acts rather than incidental pulls. package.json carries a set of check scripts, including checks for SDK routes, SDK coverage, SDK events, SDK docs, contract shapes, chart behaviour, the dockerignore, and an audit check, plus check:versions for documentation versions. Those scripts exist because the project has several artifacts that can drift apart: the API, the dashboard, the SDK, the OpenAPI spec, the Helm chart, and the docs. An upgrade that touches the API without regenerating openapi.json and the SDK is the kind of drift those checks are designed to catch, so running the check scripts after pulling is the concrete upgrade step.

The MIT licence covers the code in this repository. It does not cover your WhatsApp account, and it does not grant you any right to operate against WhatsApp's terms. The README's compliance paragraph is a usage boundary the licence does not soften, and the reverse-engineered client libraries OpenWA depends on carry their own licences and their own relationship to WhatsApp's terms. Nothing here is legal advice; the point is that an MIT grant on the gateway is not a grant on the platform you connect it to.

Editorial conclusion

Adopt OpenWA for internal tooling, personal projects, and replies or alerts to people who already expect to hear from you, on a dedicated number you can afford to lose. Do not adopt it for anything the README calls compliance-sensitive, including healthcare, finance, large-scale commercial messaging, and EU/EEA deployments under DMA or GDPR framings; the project itself says to treat it as not approved there and use Meta's official WhatsApp Cloud API. Before committing, verify three things: whether you need the built-in datastore orchestration, because leaving it on means running the docker-proxy service and accepting that a compromised API container could create containers with host bind-mounts; whether your host has the memory for whatsapp-web.js at roughly 300 to 500 MB per session; and whether your workload survives the documented first-message-to-a-new-contact delivery drop, which the API reports as success.

Frequently asked questions

What is OpenWA?

OpenWA is a self-hosted WhatsApp API gateway written in TypeScript under the MIT licence. It exposes sessions and message sending over an HTTP API, with a React dashboard for managing sessions, webhooks, and API keys, and it connects through the reverse-engineered whatsapp-web.js and baileys clients rather than Meta's official Cloud API.

Is there an open source WhatsApp bot available?

OpenWA is one option: it is open source under MIT and provides an HTTP API plus webhooks that a bot can be built on, and the project maintains official plugins for Chatwoot and Typebot and points at community n8n nodes. Its README warns that it uses unofficial clients, so any bot built on it carries a non-zero risk of account restriction.

How do I use OpenWA?

Copy .env.example to .env, start the stack with docker compose up -d, then use the dashboard to create a session, link a number by scanning the QR code, and issue an API key. After that, call the HTTP API or subscribe a webhook; the repository ships an openapi.json and an SDK directory, and the README has an API examples section.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rmyndharis-openwa.svg)](https://hysenlabs.com/projects/rmyndharis-openwa)