Repository navigation
Configuration
- Go to WhatsApp > WhatsApp Account in Frappe Desk
- Open your existing account (or create one)
- Scroll to the OpenWA Gateway section
- Check OpenWA Enabled
- Fill in the required fields
- Click Setup OpenWA button — auto-creates a session, starts it, and displays a QR code
- Scan the QR code with your phone — status changes to "Connected"
Note: The Session ID is auto-populated by the Setup button. You don't need to create sessions manually in OpenWA.
| Field | Type | Description | Example |
|---|---|---|---|
| OpenWA Enabled | Check | Enable OpenWA routing | Yes |
| OpenWA Base URL | Data | Gateway URL | http://localhost:2785 |
| OpenWA Session ID | Data (read-only) | Auto-populated by Setup | UUID |
| OpenWA API Key | Password | API key for authentication | owa_k1_... |
| OpenWA Webhook Secret | Password | Shared secret for HMAC verification | my-shared-secret-123 |
| Field | Type | Default | Description |
|---|---|---|---|
| Require HMAC Signature | Check | Off | Reject webhooks without valid HMAC signature |
| API Timeout (seconds) | Int | 30 | HTTP timeout for OpenWA REST API calls |
| Session Start Timeout (seconds) | Int | 60 | HTTP timeout when starting a session |
| Webhook Rate Limit (req/min) | Int | 60 | Max inbound requests per minute per IP |
| Circuit Breaker Threshold | Int | 5 | Consecutive failures before circuit breaker trips |
| Circuit Breaker Cooldown (seconds) | Int | 300 | Seconds to wait before retrying after trip |
| Max Outbox Retry Attempts | Int | 100 | Max delivery retries per outbox entry |
| Field | Type | Description |
|---|---|---|
| QR Code | HTML | QR code display area (auto-populated) |
The form uses depends_on to show/hide fields based on the OpenWA mode:
| Mode | Visible | Hidden |
|---|---|---|
| OpenWA enabled | OpenWA fields (Base URL, Session ID, API Key, Webhook Secret, Settings, QR Code) | Meta fields (Token, URL, Version, Phone ID, App ID, Business ID) |
| OpenWA disabled | Meta fields | OpenWA fields |
| Both modes | Account Name, Status, Is Default Incoming/Outgoing, Allow Auto Read Receipt, OpenWA Enabled | — |
When you save a WhatsApp Account with OpenWA enabled, the app automatically:
- Lists all webhooks in OpenWA
- Finds a webhook matching the Frappe URL
- If found → updates the secret
- If not found → creates a new webhook using the account's Webhook Events field
The event list sent to OpenWA is resolved as follows:
| Source | Events |
|---|---|
Webhook Events (openwa_webhook_events) field, when non-empty |
Whatever JSON array is stored on the account — this is the source of truth |
| Field empty (fallback) | The built-in 17-event default: message.received, message.sent, message.ack, message.failed, message.revoked, message.reaction, message.edited, status.received, session.status, session.qr, session.authenticated, session.disconnected, session.reconnect_loop, group.join, group.leave, group.update, call.received
|
| New account (fixture default) | 11 events: message.received, message.sent, message.ack, message.failed, message.revoked, message.reaction, message.receipt, session.status, session.qr, session.authenticated, session.disconnected
|
Editing the field and saving the account re-syncs the webhook with the new list.
Create a webhook in OpenWA (via dashboard or API):
-
URL:
https://your-site.local/api/method/openwa_bridge.inbound.receive_openwa_message - Method: POST
-
Events:
-
message.received— incoming messages -
message.sent— outgoing message acknowledgments -
message.ack— delivery/read receipts -
message.failed— send failures -
message.revoked— deleted messages -
message.reaction— emoji reactions -
message.edited— sent message text edited -
message.receipt— per-user delivery/read/played receipts (opt-in, see note below) -
status.received— status/story posts -
session.status— session status changes -
session.qr— QR code generated -
session.authenticated— session authenticated -
session.disconnected— session disconnected -
session.reconnect_loop— session stuck in reconnect loop -
group.join— participant joined group -
group.leave— participant left group -
group.update— group metadata changed -
call.received— incoming call ringing
-
-
Headers:
X-OpenWA-Signature(auto-generated by OpenWA)
message.receiptis opt-in and never included via a wildcard. OpenWA only emits it when the individual event name is explicitly subscribed (a*subscription does not cover it), and it is Baileys-only. Subscribe to it explicitly if you need per-participantdelivered/read/playedstatus.
Go to WhatsApp > OpenWA Bridge Settings for system-wide configuration:
| Field | Default | Description |
|---|---|---|
| Idempotency TTL | 3600 | How long to remember duplicate webhook events (seconds) |
| Outbox Batch Size | 25 | Max outbox entries re-enqueued per scheduler cycle |
| Max Inbound Media Size | 10 | Maximum size for inbound media attachments (MB) |
These are OpenWA-side environment variables that affect the bridge:
| Variable | Default | Description |
|---|---|---|
SIMULATE_TYPING |
true |
Built-in typing simulation before sends (anti-ban). Set false if using bridge's _send_typing_indicator to avoid duplicates. |
SIMULATE_TYPING_MAX_MS |
5000 |
Max pause (ms) for simulated typing |
RESOLVE_LID_TO_PHONE |
false |
Attach senderPhone to message.received webhooks via LID→phone lookup |
WEBHOOK_CONTACT_DETAILS |
false |
Expose full contact field set (id, number, labels, etc.) on webhooks |
MEDIA_DOWNLOAD_MAX_BYTES |
52428800 |
Cap for inbound/outbound media (default 50 MiB) |
STORE_EPHEMERAL_MESSAGES |
true |
Persist disappearing messages |
BAILEYS_SYNC_FULL_HISTORY |
false |
Pull full message history on connect (large) |
METRICS_TOKEN |
— | Enable Prometheus metrics at GET /api/metrics
|
AUDIT_RETENTION_DAYS |
90 |
Days to keep audit logs (0 = forever) |
CORS_ORIGINS |
* |
Comma-separated allowed origins (must set explicit origins in production) |
HOST |
127.0.0.1 |
Bind to 0.0.0.0 for external access |
You can also configure credentials in site_config.json:
{
"openwa_api_key": "owa_k1_...",
"openwa_api_secret": "your-api-secret",
"openwa_webhook_secret": "my-shared-secret-123",
"openwa_webhook_id": "your-webhook-id"
}OpenWA blocks webhook delivery to private/internal addresses by default (SSRF protection). When OpenWA and Frappe are on the same server, you must whitelist the Frappe site so webhooks can be delivered.
Recommended (secure) — keep global protection ON, whitelist your Frappe site and IP:
# In ~/OpenWA/.env
SSRF_ALLOWED_HOSTS=your-site-name,your-server-ip,localhost,127.0.0.1,minio
# your-site-name = your Frappe/ERPNext site name (e.g., my-site)
# your-server-ip = the IP used to connect from VM to host (e.g., 192.168.1.15)
# minio = MinIO service hostname (if used for file storage)Why include the site name? OpenWA resolves webhook target hosts. When Frappe's webhook URL uses a site name (e.g.,
your-site-nameorerp.example.com), that hostname must appear in the allow list — otherwise OpenWA treats it as an internal host and blocks delivery. If you use MinIO for file storage, includeminioas well.
Alternative (closed networks only) — disable protection entirely:
WEBHOOK_SSRF_PROTECT=falseAfter updating .env, restart:
sudo systemctl restart openwaThe app includes scheduled tasks that automatically restart disconnected OpenWA sessions:
- Hourly: Checks all OpenWA accounts — restarts any disconnected sessions
- Daily: Backup health check + outbox cleanup
Enable the scheduler:
bench --site your-site.local scheduler enable
bench restartPre-send check: Before every outbound message, the system verifies the session is ready. If not, it automatically attempts a restart and waits for the session to recover.
Status sync: The WhatsApp Account status field (Active/Inactive) is automatically updated to match the OpenWA session status.
Set Send Type to Jinja and write a Jinja Code template:
Dear {{ doc.customer_name }},
Your invoice {{ doc.name }} is ready.
Amount: {{ doc.grand_total }} {{ doc.currency }}
Due Date: {{ doc.due_date }}
Thank you!- Create a template at WhatsApp > WhatsApp Templates
- Use
{{1}},{{2}}placeholders - Set Send Type to
Template - Map fields in the Fields child table
- Create a template with Dynamic Image Header checked
- Set a Print Format for image rendering
- Optionally configure Letter Head
- The doc is rendered as PNG and sent before the template text
- Off (default): Webhooks without a signature are processed with a warning
- On: Only webhooks with a valid HMAC-SHA256 signature are accepted
Use strict mode if you've enabled HMAC on the OpenWA side.
Controls how long Frappe waits for OpenWA API responses. Increase if:
- OpenWA is on a remote server
- Network latency is high
- Sending large media files
After N consecutive send failures on an account:
- The circuit "opens" — all sends are blocked
- After the cooldown period, a test send is attempted
- If successful, the circuit "closes" and normal operation resumes
- If failed, the circuit reopens
This prevents cascade failures when OpenWA is down.
Each outbound message goes through the outbox queue. If sending fails:
- Retry with exponential backoff (30s, 60s, 120s, 300s, capped at 1h)
- After this many attempts, the entry is marked as "Failed"
- Old entries are cleaned up daily (>7 days for Sent, >30 days for Failed)