Skip to main content
GOWA has two separate kinds of authentication:
  1. API access: who may call GOWA. This is HTTP Basic Auth.
  2. WhatsApp pairing: linking a WhatsApp account to a device slot, as you would link WhatsApp Web.

API access: Basic Auth

Basic Auth is the only API authentication method. There are no API keys or JWTs for REST calls. Set one or more user:password pairs, comma-separated:
A malformed entry (for example one without :) stops the server at startup. When APP_BASIC_AUTH is empty, the API is unauthenticated; only do this on a trusted network. Send credentials on every request:
A missing or wrong credential returns 401 Unauthorized.

What is not behind Basic Auth

Everything else, including the dashboard at /, /mcp and /ws, requires Basic Auth when it is configured.

WebSocket

Browsers cannot set headers on WebSocket connections, so /ws takes the credential in the query string:
authorization is base64(user:password). Use TLS (wss://), since the credential is visible in the URL and in proxy logs.

CORS

CORS allows any origin by default. Restrict it for browser clients with APP_CORS_ALLOWED_ORIGINS=https://ui.example.com (comma-separated). Allowed request headers are Origin, Content-Type, Accept, Authorization and X-Device-Id.

WhatsApp pairing

Every WhatsApp account lives in a device slot. Create the slot first, then pair it. Routes under /devices/{device_id} take the device from the path. The equivalent /app/* routes use the X-Device-Id header.

QR code

Open qr_link and scan it from the phone: Settings → Linked devices → Link a device. Request a new code after qr_duration seconds. The dashboard at / shows the QR directly and updates live over the WebSocket.

Pairing code

The response contains results.pair_code, an 8-character code. On the phone, choose Link with phone number instead and type it in. phone must be the number of the account being linked, with country code.

Passkey

If the phone asks for passkey verification during QR login, GOWA stores a WebAuthn challenge and broadcasts PASSKEY_REQUEST on the WebSocket. The dashboard handles this flow. To drive it yourself:
  1. GET /app/passkey returns the pending challenge.
  2. POST /app/passkey/response submits the WebAuthn assertion (PublicKeyCredential.toJSON() format).
  3. If the assertion arrives after the 5-minute handoff window, GOWA emits PASSKEY_CONFIRMATION with a code. Confirm it matches the phone, then call POST /app/passkey/confirm.
Send X-Device-Id on all three calls.

Check the result

results.is_connected and results.is_logged_in are both true once the account is linked. Sessions persist in storages/, so restarts do not require pairing again.

Logging out and removing

An account unlinked from the phone (Linked devices → Log out) is logged out the same way: the slot remains and needs pairing again.

Security checklist

  • Always set APP_BASIC_AUTH with strong passwords on anything reachable from a network.
  • Terminate TLS at a reverse proxy; Basic Auth credentials are only base64-encoded.
  • Restrict APP_CORS_ALLOWED_ORIGINS when a browser UI calls the API.
  • Change WHATSAPP_WEBHOOK_SECRET from its default secret.
  • Treat storages/ as sensitive: it contains the WhatsApp session keys of every linked account.