Repository navigation
Troubleshooting
Cause: frappe_whatsapp's wildcard doc_events hook fires send_template_message() on an OpenWA account where the token field is empty.
Fix: Ensure openwa_bridge is installed and overrides are active. The override resolves the account before checking _is_openwa_account() and skips the Meta token read when the account is OpenWA.
- Verify OpenWA session is connected: check the OpenWA dashboard
- Confirm OpenWA Enabled is checked on the WhatsApp Account
- Check the Frappe error log for API response details
- Verify the API key matches between OpenWA and Frappe
- Check circuit breaker status — if tripped, wait for cooldown or reset
The bridge sends every outbound request with an Idempotency-Key header derived
from the WhatsApp Message name (frappe-{name}, or frappe-{name}-hdr for the
dynamic header image). OpenWA enforces uniqueness on (sessionId, idempotencyKey)
for 24 hours, independent of route.
| Error | Meaning | Fix |
|---|---|---|
409 IDEMPOTENCY_KEY_IN_PROGRESS |
The original request with this key is still running | Wait and let the outbox worker retry; do not resend under a new key |
409 IDEMPOTENCY_OUTCOME_UNKNOWN |
OpenWA lost track of the result (e.g. restart mid-send) | Check the message in WhatsApp before retrying — resending may duplicate |
422 IDEMPOTENCY_KEY_REUSED |
The same key was used with a different payload | The key is derived from the document name and cannot be edited. Create a new WhatsApp Message (new name → new key) instead of re-sending changed content under the old doc |
The error text shown in OpenWA API Error is parsed from error.message /
error.code (and top-level message/code), so the code above appears verbatim.
A replayed request returns Idempotent-Replayed: true and the original response.
message.receipt carries per-participant delivered / read / played status
and is never delivered through a * wildcard. To receive it:
- Subscribe to
message.receiptexplicitly on the OpenWA webhook (or add it to the account's Webhook Events field and save). - Confirm OpenWA is running the Baileys engine — other engines do not emit it.
- Check OpenWA Event Log for
message.receiptrows; the handler requires both a message id (messageIdorid) and aparticipant, and silently drops events missing either.
Note this is separate from message.ack, which reports your own outbound
message's delivery/read state.
- Check if the template exists in OpenWA — the bridge auto-recovers stale IDs, but verify the OpenWA Template ID field is populated
- Verify the Fields child table on the notification maps correctly to document field names
- Check the OpenWA API Error log entry for the full response body
- Verify PyMuPDF is installed:
bench pip install PyMuPDF - Check that the Print Format for Header field is set on the template
- Verify the Dynamic Image Header checkbox is checked
- If you want letterhead: Include Letter Head must be on AND Letter Head for Image must be selected
- Look for "OpenWA: Dynamic header image failed" in error logs
- OpenWA's
send-imageendpoint requiresmimetype: "image/png"in the payload - Both Template and Jinja send types support dynamic image headers
- Verify OpenWA is running:
curl http://localhost:2785/api/sessions - If OpenWA is down, restart it:
cd ~/OpenWA && pm2 restart openwa-gateway - The setup flow has increased timeouts (30s for API calls, 60s for session start)
- Check that
openwa_base_urlmatches your OpenWA address
- Test the HMAC signature: the
X-Openwa-Signatureheader must matchsha256=<hex> - Check the webhook URL is accessible from OpenWA
- Verify
SSRF_ALLOWED_HOSTSincludes your Frappe site name, server IP, and MinIO (e.g.,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)
-
- Review OpenWA webhook logs for delivery status
- Check the webhook's event list: the account's Webhook Events field wins when set; otherwise the bridge syncs its 17-event default (
message.edited,status.received, group and call events included). A blank field on a new account starts from the 11-event fixture default —message.receiptis in that list but not in the 17-event fallback, so add it explicitly if you rely on receipts.
- Enable Frappe scheduler:
bench --site your-site scheduler enable - Verify scheduler is running:
bench --site your-site doctor - Check OpenWA is running:
curl http://localhost:2785/api/sessions - The pre-send check auto-restarts sessions on next message attempt
- If session shows "requires QR re-scan", open WhatsApp Account form and scan again
Symptoms: Session connects successfully, shows "Connected" for a few minutes, then drops to "Failed" or "Disconnected".
Root cause: OpenWA auto-downloads media from all incoming messages. When reconnecting after a brief disconnection, WhatsApp Web queues messages — the catch-up flood of media downloads overwhelms Chrome/Puppeteer and crashes the session.
Fix:
- Disable media pre-download in OpenWA (our bridge handles media on-demand via webhooks):
# Edit OpenWA .env
nano ~/OpenWA/.env
# Add these lines:
MEDIA_DOWNLOAD_ENABLED=false
STORE_EPHEMERAL_MESSAGES=false
# Delete the generated env that overrides your settings
rm -f ~/OpenWA/data/.env.generated
# Restart OpenWA
pm2 restart openwa-gateway- Verify the Frappe scheduler is running (health check needs it):
bench --site your-site scheduler enable
bench --site your-site doctor- Check server resources (Chrome needs ~300MB RAM per session):
free -h- Test internet connectivity to WhatsApp:
curl -I https://web.whatsapp.com --connect-timeout 5Recovery: The health check (hourly) now automatically handles failed sessions:
- Tries force-kill first (keeps auth, no QR re-scan needed)
- If still failed, deletes and recreates the session (QR re-scan required)
Cause: Chrome/Puppeteer crash in OpenWA (browser session died), or session got stuck.
Fix (automatic):
- The form shows "Session Failed — Reconnect to Fix" indicator
- Click "Reconnect" → confirms
- The app first tries force-kill (SIGKILLs the crashed Chrome process, keeps session data — no QR re-scan needed)
- If force-kill doesn't help, it deletes and recreates the session (QR re-scan required)
Fix (manual, if automatic doesn't work):
- In OpenWA dashboard (
localhost:2785/sessions), click "Kill Stuck" on the session - Then click "Reconnect" — this restarts the session with existing auth data
- If that fails too, click "Delete" in OpenWA, then clear the OpenWA Session ID in Frappe and click "Setup OpenWA"
Cause: You deleted the session from OpenWA's dashboard (localhost:2785/sessions), but the WhatsApp Account in Frappe still holds the old session UUID.
Fix:
- The form now detects this automatically and shows a "Setup OpenWA (New Session)" button
- Click it — it clears the stale session ID and reloads the form
- Click "Setup OpenWA" to create and connect a new session
If the button doesn't appear, you can manually clear the session ID:
- Open the WhatsApp Account in Frappe Desk
- Clear the OpenWA Session ID field
- Save
- Click "Setup OpenWA"
Cause: Notification flow with dynamic header was sending both image and text as separate messages.
Fix: This was a known bug that has been fixed. Update to the latest version.
The app includes fallback logic — if frappe.get_doc() fails, it falls back to frappe.db.get_value(). Check the error log if templates aren't rendering correctly.
- Check if the background worker is running:
bench --site your-site doctor - Check for circuit breaker trips in error logs
- Verify the scheduler is running for the safety-net sweep
- Check Redis connectivity (used for distributed locks)
The inbound webhook has rate limiting (default 60 req/min per IP). If OpenWA is sending too many retries:
- Increase the rate limit in WhatsApp Account Settings
- Or check why OpenWA is retrying excessively (may indicate a processing issue)
This means a template with the same name already exists in OpenWA. The bridge handles this automatically:
- If the template ID is stale, it looks up by name and updates
- If no match, it creates a fresh template
If issues persist, try deleting the template from the OpenWA dashboard and re-saving in Frappe.
The bridge supports @lid JID suffixes (WhatsApp LID format). If you see unknown sender IDs, check that strip_jid_suffix() in utils.py includes @lid.
Cause: OpenWA POST /messages/reply is text-only — ReplyMessageDto = chatId + quotedMessageId + text. The send routes (send-image, send-video, send-audio, send-document) accept no quote parameter.
Workaround: When a media message is sent with is_reply:
- Send the media unquoted via
send-{type}(withlinkorbase64payload). - Follow up with a text reply quoting the just-sent media
messageId.
Both messages are delivered; the WhatsApp Message doc tracks the media messageId. A
failure of the follow-up reply is logged (OpenWA Media-Reply Workaround Failed) but
does not fail the send.
Cause: OpenWA GET /api/webhooks/delivery-failures only works with an ADMIN-role
API key. Non-ADMIN keys get 403 (expected — skipped silently).
Note: The bridge reconciles missed inbound via replay_webhooks() instead, and the
daily check_webhook_delivery_failures() writes webhook.delivery_failure rows to the
OpenWA Event Log when an ADMIN key is available.
| Item | Value |
|---|---|
| Server IP | 192.168.1.15 |
| OpenWA API | http://localhost:2785 |
| OpenWA Dashboard | http://localhost:2886 |
| Frappe bench | ~/frappe-bench |
| Site | your-site.local |
Frappe Desk → Setup → Error Log
Filter by: "OpenWA"
# PM2 logs
pm2 logs openwa-gateway
# Or check OpenWA dashboard at localhost:2886# Health check
curl http://localhost:2785/api/sessions
# Send test text
curl -X POST http://localhost:2785/api/sessions/<sessionId>/messages/send-text \
-H "Content-Type: application/json" \
-H "X-API-Key: <api-key>" \
-d '{"chatId": "967777715787@c.us", "text": "Test from API"}'bench --site your-site doctor
bench --site your-site scheduler status-
send-template is text-only: It renders header+body+footer → joins with
\n\n→ sends as plain text viasendText() -
Template vars are
Record<string, string>: Keys must match{{key}}in the template body - Template uniqueness: Names are unique per session (not globally)
-
SSRF blocks private IPs: Must set
SSRF_ALLOWED_HOSTSin OpenWA's.env— include your Frappe site name, server IP, and MinIO (e.g.,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)
-
-
Webhook signature header:
X-Openwa-Signature(lowercase 'wa') -
HMAC format:
sha256=<hex-digest>ofHMAC-SHA256(secret, raw_body)