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.
- Docker with Docker Compose (
docker compose versionshould work). - A phone with WhatsApp installed — you'll scan a QR code with it.
curlandgiton the command line.- A second WhatsApp number to receive your test message (sending to yourself also works).
What you'll build
OpenWA targets version v0.7.6. Every example uses the local base URL http://localhost:2785/api and authenticates with the X-API-Key header.
1. Run OpenWA
Clone the repository and start the development stack. The dashboard is bundled into the API image, so one command brings up everything on port 2785.
git clone https://github.com/rmyndharis/OpenWA.git
cd OpenWA
docker compose -f docker-compose.dev.yml up -d
The first run builds the image from source, so expect several minutes of build output before the container starts — this is a one-time cost, and later up commands reuse the cached image and start in seconds.
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, which you'll use to read your API key in the next step.
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", "version": "0.7.6" }
Once it responds, these URLs are live:
| Surface | URL |
|---|---|
| Dashboard | http://localhost:2785 |
| API base | http://localhost:2785/api |
| Swagger (interactive API docs) | http://localhost:2785/api/docs |
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. Read it from your clone:
cat data/.api-key
owa_k1_3f8c0a1b9d4e7f2a6c5b8e0d1a2f3c4b5d6e7f8091a2b3c4d5e6f70812a3b4c5
Export it so the commands below can reuse it:
export API_KEY="$(cat data/.api-key)"
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.
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": "sess_123e4567-e89b-12d3-a456-426614174000",
"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="sess_123e4567-e89b-12d3-a456-426614174000"
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": "sess_123e4567-e89b-12d3-a456-426614174000",
"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"
}
The QR code expires after a short window. If GET /qr returns 400 with the status no longer qr_ready, the code rotated — fetch it again to get a fresh one.
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 — the message is there.
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 sess_... value from step 3), not its name — the SDK builds the path /api/sessions/{id}/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="sess_123e4567-e89b-12d3-a456-426614174000"
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
| Symptom | Cause | Fix |
|---|---|---|
401 Unauthorized on any call | Missing or wrong X-API-Key | Re-run export API_KEY="$(cat data/.api-key)" and resend the header. |
404 Not found on session calls | Wrong SESSION_ID | Confirm the id from step 3; list sessions with GET /api/sessions. |
409 Conflict creating a session | A session with that name already exists | Choose a different name. |
400 Bad Request creating a session | name fails validation — under 3 or over 50 characters, or has disallowed characters | Use a 3-50 character name of letters, digits, and hyphens only. |
GET /qr returns 400 | QR not ready yet, or it already rotated/authenticated | Wait a moment after start, then re-fetch; if linked, the session is past the QR stage. |
400 on send-text | Session isn't ready, or the chatId is malformed | Poll the session until status is ready; use <number>@c.us with no + or spaces. |
Session stuck at authenticating after scanning | WhatsApp Web version mismatch on a slow first boot | See Troubleshooting. |
Next steps
You have a linked session sending messages. From here:
- Connect a session — session states, the QR flow, and lifecycle in depth.
- 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.