Skip to main content
Version: v0.7.6

Troubleshooting & FAQ

Find the symptom, read the cause, apply the fix. This page covers the failures operators hit most — container, session connect, QR timeout, disconnects, send failures, webhooks, auth, media, memory, and SQLite locks — then lists every HTTP status code and application error code the API returns.

For first-time setup, see Installation and Configuration. Terms used here are defined in the Glossary.

Quick diagnostics

Run these first — they isolate which layer is failing (process, database, engine, or a single session).

# 1. Is the API process up? (no auth required)
curl http://localhost:2785/api/health

# 2. Are the database and adapters ready? (no auth required)
curl http://localhost:2785/api/health/ready

# 3. Inspect one session's status
curl -H "X-API-Key: YOUR_API_KEY" \
http://localhost:2785/api/sessions/SESSION_ID

# 4. Container status and recent logs
docker compose ps
docker compose logs openwa --tail=100

YOUR_API_KEY is the key you set in your environment; see Authentication for how it is issued. SESSION_ID is the session id returned when you create a session — a generated UUID, not the name you chose. Lookups (and the auth-folder paths below) use this UUID, so paste the id from the create response, not your session name.

A healthy session returns "status": "ready". Any other value points you at a section below.

Session states

Most connection problems are a session stuck in a non-ready state. A session moves through these seven values (lowercase, as returned by the API):

StatusMeaningWhat to do
createdThe session record exists but the engine has not startedStart the session
initializingThe engine is starting upWait; if it never advances, check logs
qr_readyA QR code is waiting to be scannedFetch and scan the QR within ~60 s
authenticatingQR scanned, linking the deviceWait; if stuck, see the section below
readyConnected and able to send/receiveNone — this is the goal state
disconnectedThe link droppedReconnect; check the phone's linked devices
failedA terminal engine error occurredRead lastError, fix, recreate

When status is failed, the session response includes a human-readable lastError. See Sessions for the full lifecycle.

Connection issues

Container won't start

Problem: docker compose up fails, the container exits immediately, or you see "port already in use."

Cause: Another process is bound to port 2785, or a previous run left a broken container or volume state.

Fix:

# Find and free whatever holds the port
lsof -i :2785
kill -9 $(lsof -t -i:2785)

# Read why the container exited
docker compose logs openwa

# Pull the current image and restart cleanly
docker compose pull
docker compose up -d

If it still exits, run docker compose logs openwa and match the first error to a section below.

Session won't connect (stuck at initializing or qr_ready)

Problem: A QR code is generated but the session never reaches ready, or it falls back to disconnected after you scan.

Cause: An expired QR, a corrupted auth folder, a browser crash, or an outbound network/firewall block.

Fix: Match the cause, then re-scan.

CauseFix
Expired QRFetch a fresh one — a QR is valid for about 60 seconds
Corrupted auth folderDelete the session's auth data and re-scan
Browser crash (whatsapp-web.js)Restart the container
Network / firewall blockVerify outbound connectivity and any proxy
# whatsapp-web.js engine (default): auth lives under SESSION_DATA_PATH
# (default ./data/sessions), in a session-<id> subfolder
rm -rf ./data/sessions/session-SESSION_ID

# Baileys engine: auth lives under BAILEYS_AUTH_DIR (default ./data/baileys)
rm -rf ./data/baileys/SESSION_ID

docker compose restart openwa
warning

Deleting an auth folder unlinks that session from WhatsApp. You must scan a new QR to reconnect.

Session stuck at authenticating, never reaches ready

Engine-specific

This affects the whatsapp-web.js engine only. With ENGINE_TYPE=baileys, skip this section.

Problem: After scanning, the phone links the device but the session stays at authenticating indefinitely. Common on ARM64 hosts (for example a Raspberry Pi).

Cause: whatsapp-web.js auto-selects a WhatsApp Web client version, and an incompatible version stalls the post-link sync.

Fix: Pin a known-good WhatsApp Web version, then restart the container:

WWEBJS_WEB_VERSION=2.3000.1040641150-alpha

Browse available versions in the html/ folder of wppconnect-team/wa-version. Set the value to latest, auto, or off (or leave it unset) to restore auto-selection.

QR generation times out on slow first boot (WSL2 / low-resource)

Engine-specific

whatsapp-web.js only.

Problem: On the first launch, no QR appears and the session fails after about 30 seconds — often inside WSL2 or a resource-constrained container.

Cause: whatsapp-web.js waits a fixed 30000 ms for WhatsApp Web to finish loading before it generates the QR. A slow first boot can exceed that window.

Fix: Raise the boot/inject wait (in milliseconds) and restart:

WWEBJS_AUTH_TIMEOUT_MS=120000 # allow up to 2 minutes

Leave it unset to keep the 30000 ms default.

Session fails to launch — chrome_crashpad_handler: --database is required

Engine-specific

whatsapp-web.js (Chromium/Puppeteer) only.

Problem: The session never starts. The log shows Failed to launch the browser process with chrome_crashpad_handler: --database is required. Seen on hardened, read_only containers.

Cause: Chromium resolves its home directory from the system passwd entry and ignores $HOME. The non-root runtime user has no home directory, so on a read-only rootfs Chromium aborts at launch.

Fix: Give Chromium writable config and cache directories. The bundled image and docker-compose.yml already do this on a tmpfs /tmp. For a custom container, set both paths to a writable, existing location and mount a writable /tmp:

XDG_CONFIG_HOME=/tmp/.config
XDG_CACHE_HOME=/tmp/.cache
# Pre-create both as the runtime user, and mount a writable tmpfs at /tmp:
# compose: tmpfs: ["/tmp"]
# k8s: an emptyDir volume mounted at /tmp
danger

Do not work around this by removing --no-sandbox hardening or using seccomp:unconfined. It does not help and it widens the attack surface.

Frequent disconnections

Problem: A session drops to disconnected every few hours and needs frequent re-scans.

Cause: A logout from the phone's linked devices, memory pressure, an unstable network, or a blocked IP address.

Fix:

  • Open WhatsApp on the phone → Linked devices and confirm the device is still linked.
  • Give the container more RAM (see High memory usage).
  • Check outbound connectivity from the host.
  • If your hosting IP is being blocked by WhatsApp, route through a residential proxy.

Messaging issues

Messages not sending

Problem: The send call returns 2xx but the message isn't delivered, or it returns an error.

Cause: Most often a bad recipient format, a disconnected session, rate limiting, or oversized media.

Fix: Match the status code:

CauseStatusFix
Invalid recipient format400Use the JID format 628123456789@c.us
Session not connected409Wait for ready or reconnect the session
Rate limited429Slow your send rate (see Rate limiting)
Media too large413Reduce the file or raise the media cap
Number not on WhatsApp2xx, no deliveryVerify the number first

Verify a number is on WhatsApp before sending:

curl -H "X-API-Key: YOUR_API_KEY" \
"http://localhost:2785/api/sessions/SESSION_ID/contacts/check/628123456789"
{
"number": "628123456789",
"exists": true,
"whatsappId": "628123456789@c.us"
}

Recipient JIDs follow a fixed shape:

RecipientJID formatExample
Individual<number>@c.us628123456789@c.us
Group<groupId>@g.us120363123456789@g.us

See Sending messages for the full message API and Groups for group sends.

Webhook not firing

Problem: Messages arrive in WhatsApp but your webhook endpoint is never called.

Cause: No webhook is configured for the session, the URL is unreachable from the container, or your endpoint returns a non-2xx and exhausts the retries.

Fix:

# 1. Confirm a webhook is registered for the session — note its id
curl -H "X-API-Key: YOUR_API_KEY" \
http://localhost:2785/api/sessions/SESSION_ID/webhooks

# 2. Force a delivery with the built-in test endpoint (WEBHOOK_ID from step 1).
# It POSTs a test payload to your URL and returns the delivery result.
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
http://localhost:2785/api/sessions/SESSION_ID/webhooks/WEBHOOK_ID/test

# 3. There is no delivery-history log — read the server logs for the attempt
docker compose logs openwa --tail=200 | grep -i webhook

# 4. Confirm your endpoint accepts an unauthenticated POST quickly
curl -X POST https://your-app.example.com/webhook \
-H "Content-Type: application/json" \
-d '{"test": true}'

Your endpoint must return a 2xx quickly. OpenWA retries failed deliveries with a fixed-delay backoff, controlled by these environment variables:

VariableDefaultControls
WEBHOOK_TIMEOUT10000Per-attempt timeout, in milliseconds
WEBHOOK_MAX_RETRIES3Retry attempts after the first failure
WEBHOOK_RETRY_DELAY5000Delay between attempts, in milliseconds
tip

If the container can reach the public internet but not your endpoint, the URL is probably resolving to a host-only address. Use host.docker.internal (Docker Desktop) or the service name on a shared Docker network rather than localhost.

See Webhooks for the event list and payload shapes.

Media upload fails

Problem: A send returns 413 Payload Too Large, or media won't upload.

Cause: OpenWA enforces two separate limits. A decoded media blob may not exceed MEDIA_DOWNLOAD_MAX_BYTES (default 50 MiB), and the whole HTTP request body is bounded by BODY_SIZE_LIMIT. A base64 payload counts against both.

Fix:

  • Prefer sending media by URL for large files — the engine downloads it server-side instead of inflating your request body.
  • If you must send large base64 media, raise both limits and restart:
MEDIA_DOWNLOAD_MAX_BYTES=104857600 # 100 MiB media cap
BODY_SIZE_LIMIT=120mb # whole-request body limit

A request that exceeds the media cap returns 413 Payload Too Large with the standard error envelope — a statusCode, a message, and an error string. There is no code field on this response; branch on the status, not on a payload code.

Authentication issues

Every request returns 401

Problem: All authenticated endpoints return 401 Unauthorized, even ones that worked before.

Cause: The X-API-Key header is missing, misspelled, or carries the wrong key.

Fix: Send the header on every request to an authenticated route. The header name is exact and case-sensitive in its documented form:

# Wrong — no key, returns 401
curl http://localhost:2785/api/sessions

# Right
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:2785/api/sessions

The health endpoints (/api/health, /api/health/live, /api/health/ready) are unauthenticated by design — use them to confirm the server is up without a key. See Authentication for how keys are issued and rotated.

Performance issues

High memory usage

Problem: The container uses a large amount of RAM, or the host OOM-kills it.

Cause: With the default whatsapp-web.js engine, each session runs its own Chromium instance (roughly 300–500 MB RAM). Total memory scales with session count.

Fix: Cap memory, trim Chromium flags, or switch to the lighter engine.

services:
openwa:
deploy:
resources:
limits:
memory: 2G
environment:
# whatsapp-web.js engine only
- PUPPETEER_ARGS=--disable-dev-shm-usage,--disable-gpu,--no-sandbox

Switching to ENGINE_TYPE=baileys removes Chromium entirely and dramatically lowers per-session memory. See Scaling for capacity planning.

Database locked (SQLite)

Problem: Writes fail intermittently with SQLITE_BUSY or "database is locked," and response times spike.

Cause: SQLite serializes writes. Under concurrent sessions or a write-heavy workload, writers contend for the single database lock.

Fix: Ensure write-ahead logging is on, then plan a move to PostgreSQL.

# Data DB defaults to ./data/openwa.sqlite (auth/audit DB is ./data/main.sqlite)
sqlite3 ./data/openwa.sqlite "PRAGMA journal_mode;" # expect: wal
sqlite3 ./data/openwa.sqlite "PRAGMA journal_mode=WAL;"

Past roughly 5 concurrent sessions, or for any write-heavy deployment, switch the Database adapter to PostgreSQL. See the Migration guide.

Docker issues

Volume permission denied

Problem: "Permission denied" when writing to the data directory; auth files don't persist across restarts.

Cause: The host directory is owned by a different user than the container's runtime user.

Fix:

sudo chown -R $(id -u):$(id -g) ./data
docker compose restart openwa

Podman: Docker socket missing or container stays unhealthy

Problem: On Podman you see FileNotFoundError for the Docker socket, or the container starts but stays unhealthy.

Cause: Podman's rootless socket is inactive by default, and a few Docker conventions (unqualified image names, node -e healthchecks) behave differently under Podman.

Fix: Start the Podman socket and export DOCKER_HOST:

systemctl --user start podman.socket
systemctl --user enable podman.socket
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock

When building a custom image under Podman, use fully-qualified image names (for example docker.io/node:22-slim) and a curl-based healthcheck rather than node -e.

Rate limiting

OpenWA applies a single global rate limit across all clients (keyed on the resolved client IP), enforced in three tiers. Exceeding any tier returns 429 Too Many Requests.

TierDefault limitWindow
Short10 requests1 second
Medium100 requests60 seconds
Long1000 requests1 hour

There is no per-session rate-limit endpoint. To raise the limits, set the corresponding configuration values and restart. On a 429, back off and retry.

Error code reference

HTTP status codes

The API uses the standard NestJS error envelope. Every error response is JSON of the shape:

{
"statusCode": 404,
"message": "Session \"SESSION_ID\" not found",
"error": "Not Found"
}
CodeMeaningCommon causeFix
400Bad RequestInvalid body, params, or JID formatCheck the request payload
401UnauthorizedMissing or invalid API keySend a valid X-API-Key header
404Not FoundUnknown session, message, or routeVerify the id exists
409ConflictSession id already exists, or session not connectedUse a new id, or wait for ready
413Payload Too LargeMedia or body exceeds the capReduce size or raise the limit
429Too Many RequestsRate limit exceededBack off and retry
500Internal Server ErrorUnhandled server errorCheck the logs and report it
501Not ImplementedOperation unsupported by the active engineUse a supported engine or operation
503Service UnavailableDependency or session temporarily downRetry after the dependency recovers

Bulk-send result codes

These codes are not HTTP statuses. They appear only on per-message results inside a bulk batch, retrieved from GET /api/sessions/{sessionId}/messages/batch/{batchId}. Each failed entry carries a { code, message } object so you can branch on why one message in the batch failed. An ordinary POST .../messages send does not return these codes — it returns a normal HTTP status (see the table above).

CodeMeaningFix
SEND_BLOCKEDDestination address was refused (SSRF/allow-list block)Check the recipient and any allow/deny list
SEND_FAILEDThe engine rejected the sendRead the accompanying message for the cause
note

Engine-not-ready errors surface as 409 Conflict ("Session is not connected"), and referencing a message outside the engine's lookup window surfaces as 404 Not Found. Both follow the standard HTTP envelope above.

Frequently asked questions

How many sessions can I run on one instance? It depends on host resources and the engine. With the default whatsapp-web.js engine (~300–500 MB RAM per session): roughly 3–5 sessions on 2 GB, 8–10 on 4 GB, 15–20 on 8 GB. ENGINE_TYPE=baileys is browser-free and fits many more on the same hardware. See Scaling.

How do I keep my number from getting banned? OpenWA uses the unofficial WhatsApp Web protocol, so there is inherent risk. Use a dedicated number (not your personal one), avoid bulk or unsolicited messaging, ramp volume gradually, vary message content, add delays between sends, and don't message people who haven't contacted you first.

Can I use a WhatsApp Business account? Yes — both personal and WhatsApp Business app accounts work. The official WhatsApp Business API (Meta Cloud API) is a different product and is not what OpenWA uses.

How do I send to a group? Use the group JID (...@g.us) as the chatId. See Groups.

How do I run behind a reverse proxy? Forward WebSocket upgrades and set generous idle timeouts — OpenWA keeps a long-lived connection per dashboard client on the same port as the REST API. See Deployment.

How do I integrate with n8n? See the n8n integration guide.

What webhook events can I subscribe to? Message events (message.received, message.sent, message.ack, message.failed, message.revoked, message.reaction) and session events (session.status, session.qr, session.authenticated, session.disconnected). Group events are reserved but not currently emitted. See Webhooks.

Getting help

Before opening an issue:

  1. Re-check this page for your symptom.
  2. Read the logs: docker compose logs openwa --tail=200.
  3. Try a restart and, if relevant, a clean re-scan.
  4. Search existing issues — someone may have hit it already.

When you report, include your OpenWA version (v0.7.6), deployment (Docker/OS), engine (ENGINE_TYPE), Database adapter, session count, reproduction steps, and sanitized logs.

Next steps