Skip to main content
Version: v0.23.5

Quick Start

Send your first WhatsApp message through OpenWA in about five minutes. You'll run the gateway with Docker, grab your API key, link a WhatsApp account by scanning a QR code, and fire a real curl request that delivers a message.

This is the guaranteed happy path — one linear sequence, no branching. For production setups, custom backends, or running from source, follow the links in Next steps.

Prerequisites
  • Docker (docker --version should work). Never installed it? The Install Docker page walks through each OS.
  • A phone with WhatsApp installed — you'll scan a QR code with it.
  • curl on the command line.
  • A second WhatsApp number to receive your test message (sending to yourself also works).
  • A dedicated WhatsApp number for testing, not your daily driver — connecting any number to an unofficial gateway carries ban risk; see Ban risk & safe sending.

What you'll build​

OpenWA targets version v0.23.5. Every example uses the local base URL http://localhost:2785/api and authenticates with the X-API-Key header.

1. Run OpenWA​

The quickest start is the prebuilt multi-arch image on Docker Hub — docker pull takes seconds, where building from source spends several minutes downloading and installing Chromium. This path needs only Docker: no Compose, no clone. The dashboard is bundled into the API image, so one command brings up everything on port 2785.

mkdir -p data
docker run -d --name openwa-api -p 2785:2785 -v "$(pwd)/data:/app/data" rmyndharis/openwa:latest

This runs with zero-config defaults — SQLite for the database and local storage for media — so there's nothing to configure for a first run. The ./data directory is bind-mounted into the container, so the database, media, and the generated API key survive a restart.

New to Docker? See Install Docker first. If you would rather run the Compose stack, pin an image version, add PostgreSQL, Redis, or S3-compatible storage, or build from source, Installation covers each of those — every step below works the same on any of them.

Wait until the container is healthy, then confirm the API is up:

curl http://localhost:2785/api/health
{ "status": "ok", "timestamp": "2026-06-26T10:00:00.000Z" }

The endpoint is public; the version field is only included when the request carries a valid API key (v0.19.0+), so a keyless probe shows status and timestamp only.

Once it responds, these URLs are live:

SurfaceURL
Dashboardhttp://localhost:2785
API basehttp://localhost:2785/api
Swagger (interactive API docs)http://localhost:2785/api/docs (opt-in)

The published image sets NODE_ENV=production, which keeps Swagger off. Add -e ENABLE_SWAGGER=true to the docker run command above to serve it; the full rule is in Configuration.

2. Get your API key​

Every API request authenticates with a key sent in the X-API-Key header. On first boot, OpenWA generates a random admin key and writes the full value to data/.api-key inside the bind-mounted ./data directory. It is written owner-only (mode 0600) as the container's own user, so on Linux a host cat is refused. Read it through the container, which works the same on every platform:

docker exec openwa-api cat /app/data/.api-key
owa_k1_3f8c0a1b9d4e7f2a6c5b8e0d1a2f3c4b5d6e7f8091a2b3c4d5e6f70812a3b4c5

Export it so the commands below can reuse it:

export API_KEY="$(docker exec openwa-api cat /app/data/.api-key)"
tip

You can also view and create keys in the dashboard at http://localhost:2785 under API Keys. The full plaintext of a key is shown only once, at creation — store it somewhere safe.

warning

This default admin key has full access. It's fine for local development. Before exposing OpenWA to a network, put it behind TLS and mint a least-privilege, session-scoped key — see Authentication.

3. Create a session​

A session is one linked WhatsApp account. Create one with POST /api/sessions — only name is required. It must be 3-50 characters, using only letters, digits, and hyphens; anything outside those bounds is rejected with 400 Bad Request:

curl -X POST http://localhost:2785/api/sessions \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{ "name": "my-bot" }'

The response (201 Created) returns the session, including its generated id and current status:

{
"id": "8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a",
"name": "my-bot",
"status": "created",
"createdAt": "2026-06-26T09:00:00Z",
"updatedAt": "2026-06-26T09:00:00Z"
}

Save the id — every call below uses it. Export it for convenience:

export SESSION_ID="8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a"
note

Reusing a name that already exists returns 409 Conflict. Pick a unique name per account.

4. Start the session and scan the QR​

Starting a session boots the WhatsApp engine, which produces a QR code you link with your phone. The session moves through these states:

Start the session:

curl -X POST http://localhost:2785/api/sessions/$SESSION_ID/start \
-H "X-API-Key: $API_KEY"

A few seconds later, fetch the QR code. It returns as a base64 PNG data URL:

curl http://localhost:2785/api/sessions/$SESSION_ID/qr \
-H "X-API-Key: $API_KEY"
{
"qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"status": "qr_ready"
}

The simplest way to scan is the dashboard, which renders the QR image for you: open http://localhost:2785, select your session, and scan the displayed code.

On your phone, scan it with WhatsApp → Settings → Linked devices → Link a device.

After scanning, the status advances from qr_ready to authenticating to ready. Poll until it reports ready:

curl http://localhost:2785/api/sessions/$SESSION_ID \
-H "X-API-Key: $API_KEY"
{
"id": "8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a",
"name": "my-bot",
"status": "ready",
"phone": "628123456789",
"pushName": "John Doe",
"connectedAt": "2026-06-26T10:00:00Z",
"createdAt": "2026-06-26T09:00:00Z",
"updatedAt": "2026-06-26T10:00:00Z"
}
note

The QR code expires after a short window, and the engine replaces the cached code in place, so calling GET /qr again returns the current one. A 400 means there is no code to hand back: either the session is past the QR stage, or its connection dropped and the engine cleared the code (since v0.23.3). Check GET /api/sessions/{sessionId} to see which: at initializing the engine is reconnecting and a fresh QR follows, at ready the session is already linked.

5. Send your first message​

Once the session reports ready, send a text message with POST /api/sessions/{sessionId}/messages/send-text.

The chatId is the recipient's number in international format — no +, no spaces — followed by @c.us. For example, the Indonesian number +62 812-3456-789 becomes 628123456789@c.us.

curl -X POST http://localhost:2785/api/sessions/$SESSION_ID/messages/send-text \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"chatId": "628123456789@c.us",
"text": "Hello from OpenWA!"
}'

A 201 Created with a messageId means the message is on its way:

{
"messageId": "true_628123456789@c.us_3EB0123456789",
"timestamp": 1782813600
}

Check the recipient's phone. A 201 says the gateway handed the message to WhatsApp, not that it arrived: a number that is not on WhatsApp still answers 201.

Or use the SDK​

If you'd rather call OpenWA from JavaScript or TypeScript, the official SDK wraps these same endpoints. Install it with npm install @rmyndharis/openwa, then:

import { OpenWAClient } from '@rmyndharis/openwa';

const client = new OpenWAClient({
baseUrl: 'http://localhost:2785',
apiKey: process.env.API_KEY!,
});

const result = await client.messages.sendText(process.env.SESSION_ID!, {
chatId: '628123456789@c.us',
text: 'Hello from the OpenWA SDK!',
});

console.log(result.messageId);

The first argument to sendText is the session id (the UUID from step 3), not its name — the SDK builds the path /api/sessions/{sessionId}/messages/send-text, and the engine is resolved by id. Passing the name my-bot returns 400 "Session 'my-bot' is not active". Export the id alongside API_KEY so this snippet runs as-is:

export SESSION_ID="8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a"

The SDK adds the /api prefix for you, so baseUrl is http://localhost:2785 (no /api). See the SDK overview for the full surface.

Troubleshooting​

SymptomCauseFix
401 Unauthorized on any callMissing or wrong X-API-KeyRe-run export API_KEY="$(docker exec openwa-api cat /app/data/.api-key)" and resend the header.
400 Validation failed (uuid is expected) on session callsSESSION_ID is not a UUID (the session name was used instead of its id)Use the id from step 3, not the name.
404 Not found on session callsA well-formed id with no session behind itConfirm the id from step 3; list sessions with GET /api/sessions.
409 Conflict creating a sessionA session with that name already existsChoose a different name.
400 Bad Request creating a sessionname fails validation — under 3 or over 50 characters, or has disallowed charactersUse a 3-50 character name of letters, digits, and hyphens only.
GET /qr returns 400No code to hand back: the engine has not produced one yet, the connection dropped and cleared it, or the session is already linkedWait a moment after start, then re-fetch; check GET /api/sessions/{sessionId} for which case you are in.
400 on send-textSession isn't ready, or the chatId is malformedPoll the session until status is ready; use <number>@c.us with no + or spaces.
409 on send-textThe engine is not connected: reconnecting, or WhatsApp Web reloading its page even while the status still reads readyRetryable. Wait a few seconds and resend.
Session stuck at authenticating after scanningWhatsApp Web version mismatch on a slow first bootSee Troubleshooting.
201 but nothing arrivesThe recipient may not be on WhatsApp; a 201 is acceptance, not deliveryCheck the number with GET /api/sessions/{sessionId}/contacts/check/{number}, then follow the message's status field.

Next steps​

You have a linked session sending messages. From here:

  • Connect a session — session states, the QR flow, and lifecycle in depth.
  • Installation — the Compose stacks, PostgreSQL/Redis/S3, and running from source.
  • Configuration — environment variables and choosing database, storage, and cache backends.
  • Sending messages — media, replies, reactions, and bulk sends.
  • Webhooks — receive inbound messages and status events in real time.
  • API reference — every endpoint, field, and status code.