Requirements
- GOWA reachable from Chatwoot over a public URL (for agent-reply webhooks).
- A Chatwoot account with admin access and a user API access token.
- At least one logged-in WhatsApp device.
Set up
1
Create an API channel inbox
In Chatwoot go to Settings → Inboxes → Add Inbox → API. Note the numeric inbox ID from the inbox URL, and the webhook secret Chatwoot shows for the channel. Alternatively, let GOWA create the inbox (see Auto-provisioning).
2
Collect IDs and token
The account ID is the number in
/app/accounts/<id>/.... Copy a user access token from Profile Settings. Both IDs must be the numeric IDs, not the channel identifier or hmac_token.3
Configure GOWA
.env
4
Point Chatwoot at GOWA
Set the API channel’s webhook URL to
https://gowa.example.com/chatwoot/webhook (with APP_BASE_PATH, include the base path). This route is public: it is protected by the webhook secret, not Basic Auth.5
Test
Message the WhatsApp number from another phone; it should appear in the inbox. Reply from Chatwoot and check it arrives on WhatsApp.
Configuration
Most settings also have a
--chatwoot-* flag; CHATWOOT_URL, CHATWOOT_API_TOKEN, CHATWOOT_ACCOUNT_ID and CHATWOOT_INBOX_ID are environment-only. See the configuration reference.
Webhook secret
WhenCHATWOOT_WEBHOOK_SECRET (or a device’s webhook_secret) is set, every webhook must prove it by one of:
- Chatwoot’s signature (
X-Chatwoot-Signature+X-Chatwoot-Timestamp), recommended. Use the API channel’s own webhook secret. Replays are rejected. - An
X-Hub-Signature-256HMAC of the body. - The secret in an
X-Chatwoot-Webhook-SecretorX-Gowa-Chatwoot-Secretheader. ?secret=in the webhook URL, only for Chatwoot versions that cannot sign or add headers. The secret then appears in access logs.
401; GOWA logs the reason (signature does not match = wrong secret, timestamp outside the tolerance window = clock drift).
Auto-provisioning
WithCHATWOOT_AUTO_CREATE=true and no CHATWOOT_INBOX_ID, GOWA looks for an API inbox named CHATWOOT_INBOX_NAME at startup and reuses it, or creates one with CHATWOOT_WEBHOOK_URL as its webhook.
One inbox per device
The environment variables configure one destination shared by all devices. To route each device to its own inbox, account or Chatwoot server, store per-device configs (Basic Auth applies):webhook_url (<base>/chatwoot/webhook/<device_id>); set it as that inbox’s webhook. Other routes: GET /chatwoot/configs, GET and DELETE /devices/{device_id}/chatwoot/config.
api_tokenis stored in GOWA’s database and masked in reads;webhook_secretis never returned.chatwoot_urlmust behttp(s), without credentials, and must not resolve to private or loopback addresses. Restrict it further withCHATWOOT_ALLOWED_HOSTS.- Changing
chatwoot_url,account_idorinbox_idafter conversations are linked returns409; delete and recreate the config instead. - A config can be created before the device pairs.
- Direct database import works only in single (environment) config mode.
Import history
days_limit 3, include_media true, include_groups true. The sync reads GOWA’s local chat storage, runs in the background, and only one sync per device can run at a time. Media older than about two weeks may no longer be downloadable from WhatsApp.
The REST path cannot set original timestamps; imported messages get the import time. For self-hosted Chatwoot, CHATWOOT_IMPORT_DB_URI writes directly to Chatwoot’s PostgreSQL and preserves them.
Delivery and retries
Live messages that hit a transient Chatwoot error (network error, HTTP 429 or 5xx) are queued in local storage and retried in the background with capped exponential backoff. Other 4xx errors are logged and dropped.Troubleshooting
Run with
APP_DEBUG=true and look for log lines starting with Chatwoot: and Chatwoot Webhook:. The upstream Chatwoot guide covers direct database import and formatting rules in more depth.