Repository navigation
Changelog
Manaa-Soft edited this page Oct 22, 2026
·
16 revisions
- WhatsApp Catalog Product DocType — links ERPNext Items to WhatsApp catalog with auto-fetch of name, description, image, and price from Item
-
6 whitelisted methods in
catalog.py: get_catalog_products, get_catalog_product, sync_catalog_products, send_product_to_chat_direct, send_catalog_to_chat, get_openwa_catalog_info - Automatic fallback — when OpenWA catalog endpoints return 501, sends richly formatted text+image messages
- Client-side JS — auto-fetch from Item on link select, Sync and Send to Chat buttons
- 10 test classes in test_catalog.py covering sync, send, fallback, and summary flows
- Docs — API_REFERENCE (catalog section), ARCHITECTURE, FLOWS (Flow 12), IMPROVEMENTS
-
Optimistic concurrency error ("has been modified after you have opened it") — replaced
frappe.db.set_value()withdoc.save()in_sync_single_product()so Frappe'smodifiedtimestamp stays in sync -
Auto-fetch on re-save —
_auto_fetch_item_fields()now only runs for new documents (__islocal=1), preventing validate from touching existing doc fields during sync saves
-
send_product_to_customer— new method to send a catalog product directly to a Customer's WhatsApp number (resolves Contact → phone → chat ID)
-
Catalog sync removed entirely — removed
sync_catalog_products,sync_single_product,get_openwa_catalog_infosince OpenWA does not support WhatsApp catalog operations - No more error logs from catalog sync background attempts
- DocType controller and JS simplified — no Sync button, no sync status workflow
- API reference reduced — catalog section now documents 5 methods instead of 6
-
send_single_product_to_chatrenamed tosend_product_to_chat— always uses fallback text+image (never attempts native catalog send);send_product_to_chat_directnow delegates tosend_product_to_chat
- No more 501 errors from OpenWA catalog stubs polluting server logs
All notable changes to OpenWA Bridge are documented here.
Format: Keep a Changelog Versioning: Semantic Versioning
-
OpenWA Event Log DocType — persists non-message webhook events (
group.join,group.leave,group.update,call.received,status.received,message.reaction,webhook.delivery_failure) instead of only logging them -
Inbound event persistence —
_log_event()helper; group/call/status handlers now insert Event Log rows (best-effort, never breaks webhook processing) -
Reactions persistence —
message.reactionevents stored on the WhatsApp Message doc inopenwa_reactionsJSON field as[{emoji, sender, timestamp}]with advance-only dedupe per (emoji, sender) -
Webhook DLQ replay —
check_webhook_delivery_failures()reads OpenWA delivery-failures (limit 50/session), writes Event Log rows deduped by idempotency key, and replays viareplay_webhooks(); wired intodaily(); 403 (non-ADMIN key) skipped silently -
Scheduled send —
scheduled_at(Datetime) on OpenWA Outbox holds entries in Pending until due;openwa_scheduled_atcustom field on WhatsApp Message propagated inafter_insert; processor skips future-scheduled entries without bumping attempts -
Base64 media outbound — image/video/audio/document sends accept
{base64, mimetype, filename}in addition to{link}(mirrors sticker pattern), with a clear error when neither is supplied -
Send-as-PDF — "Send To Whatsapp" dialog can send the open document as a PDF via OpenWA
send-document;render_doc_as_pdf()helper (Chrome → wkhtmltopdf fallback), whitelistedsend_document_pdf()method, send-time PDF rendering in the outbox worker (always current, retry-safe),openwa_send_pdf/openwa_print_format/openwa_pdf_filenamecustom fields, unit tests -
Media reply workaround — OpenWA
POST /messages/replyis text-only, so media replies send the media unquoted then a text reply quoting the returned mediamessageId - Docs — Tier 2 sections in API_REFERENCE, KNOWN_ISSUES, CUSTOM_FIELDS, IMPROVEMENTS
-
Contact statuses API —
get_contact_statusesto get statuses from a specific contact -
Status media streaming —
get_status_mediato stream stored status image/video -
Channel subscribe/unsubscribe —
subscribe_channel(via invite code) andunsubscribe_channelAPIs -
Channel details —
GET /channels/:channelIdendpoint documented -
Message reactions —
get_message_reactionsAPI to retrieve reactions for a message -
Batch cancel —
cancel_batchAPI to stop a running bulk batch -
kindfield — inbound messages now carrykinddiscriminator (individual,group,channel,status,broadcast) -
Typing indicator documentation — noted OpenWA's built-in
SIMULATE_TYPINGredundancy with bridge indicator -
Message editing —
edit_messageAPI +editcontent_type for outbound message edits -
Status/Stories posting —
post_status_text,post_status_image,post_status_video,get_statusesfor WhatsApp status updates -
Call handling —
reject_callAPI +call.receivedwebhook event logging -
Chat management —
mark_chat_read,mark_chat_unread,get_chat_historyAPIs -
Full-text search —
search_messagesAPI for searching across all sessions -
Group management —
list_groups,create_group,add_group_participants,remove_group_participants,promote_group_admins,demote_group_admins,set_group_name,leave_groupAPIs -
Label management —
list_labels,add_label_to_chat,remove_label_from_chatfor WhatsApp Business labels -
Batch messaging with progress —
send_bulk_with_progressasync batch API with progress tracking -
Profile management —
set_profile_name,set_profile_status,set_profile_pictureAPIs -
Session stats —
get_session_statsfor aggregate session statistics -
Channels/Newsletters —
list_channels,get_channel_messagesfor WhatsApp Channels - Typing indicators in send flow — auto-send typing indicator before each message (non-blocking)
-
message.editedwebhook handler — updates WhatsApp Message body when sent message is edited -
session.reconnect_loopwebhook handler — alerts when session is stuck in reconnect loop -
group.join/group.leavewebhook handlers — logs group membership changes -
group.updatewebhook handler — logs group metadata changes -
call.receivedwebhook handler — logs incoming call events -
Contact management API —
check_whatsapp_number,block_contact,unblock_contact -
Typing indicators —
send_typing_indicator(typing/recording/paused states) -
Bulk messaging —
send_bulk_openwavia OpenWA/messages/send-bulk -
Message forwarding —
forward_messagevia OpenWA/messages/forward -
Message deletion —
delete_messagewith optional revoke (delete for everyone) -
Pairing code authentication —
request_pairing_codeas alternative to QR scanning -
Sticker messages —
send_sticker(URL or base64) +stickercontent type -
@mention support — auto-extracts
@NNNN@c.usJIDs from text messages -
Extended webhook events —
message.sent,message.revoked,message.reaction,message.edited,session.qr,session.authenticated,session.disconnected,session.reconnect_loop,group.join,group.leave,group.update,call.received - Inbound Communication/Lead auto-creation — creates Communication doc linked to Contact; auto-creates Lead+Contact for new numbers
- Outbox cleanup scheduler — daily cleanup of old outbox entries
- Circuit breaker — blocks sends after N consecutive failures per account
- Two-phase notification pattern — sync outbox creation + async background send
- Inbound webhook hardening — always HTTP 200, HMAC lenient/strict mode, idempotency, rate limiting
- Auto-sync webhook secret — saving WhatsApp Account auto-syncs secret to OpenWA
- Session health check — hourly/daily scheduler restarts disconnected sessions
- Pre-send session verification — auto-restarts sessions before sending messages
- Dynamic image headers — renders documents as PNG via Print Format + PyMuPDF
- SSRF validation — URL validation warns on private/internal IPs
- Role-based access — whitelisted methods check Frappe permissions
- Configurable per-account settings — HMAC strict, API timeout, session start timeout, rate limit, circuit breaker, max outbox attempts
- WhatsApp Workspace — shortcuts to all key DocTypes
- Comprehensive test suite — 40+ unit and integration tests
-
HTTP connection pooling — module-level
requests.Session()for all API calls - Distributed lock — Redis-based lock prevents duplicate outbox processing
- Replay webhooks API — fetches messages from OpenWA and creates WhatsApp Message docs
- Outbox dashboard API — system health, success rates, queue depth, circuit breaker status
- Architecture Decision Records — 5 ADRs documenting key design decisions
-
Uninstall cleanup —
before_uninstall()deletes OpenWA sessions and webhooks -
Outbound send idempotency — single-recipient sends now carry an
Idempotency-Keyheader (derived from the WhatsApp Message doc name bybuild_idempotency_key()); header images use a distinct-hdr-suffixed key so the image and its caption are deduplicated independently. Key threaded throughopenwa_api()and both direct-HTTP send paths (whatsapp_message._send_via_openwa,tasks._send_dynamic_header_for_outbox). -
message.receiptwebhook event — opt-in per-recipient delivery/read/played receipts for group and status-broadcast sends (_handle_message_receipt, persisted via_log_eventwithcontact=participant); subscribed explicitly in OpenWA (not delivered to the*wildcard). Added to the webhook event list in Settings and theOpenWA Event Logevent-type Select. -
Actionable OpenWA HTTP errors —
openwa_error_message()parses the JSON error body (message/error/code) instead of a bareraise_for_status(), so engine 400/503 andvote-poll400POLL_OPTION_NOT_FOUNDsurface the real reason; still raisesrequests.HTTPErrorso existingexceptblocks keep working.
-
Unit test file did not compile — every
MagicMock(get_password.return_value=...)used Python 3.8-removed dotted-name keyword args (SyntaxError), sotest_account.pynever parsed and nopytestrun could include it. Replaced withmock.get_password.return_value = "..."assignments across all 55 test mocks; the suite now compiles and lint/typecheck can analyze it. -
Stale session recovery — when a session is deleted from OpenWA dashboard, the Frappe form now detects it (returns
not_foundstatus) and shows a "Setup OpenWA (New Session)" button instead of getting stuck -
reset_openwa_sessionmethod — new whitelisted method to clear staleopenwa_session_id+ cache -
get_openwa_session_statusdistinguishes 404 from unreachable — returnsnot_foundwhen server is reachable but session is gone,errorwhen server is down -
Session disconnects after 1-2 minutes — health check now handles failed sessions with force-kill → delete+recreate flow; recommended
MEDIA_DOWNLOAD_ENABLED=falseto prevent media flood crashes - Health check force-kill — hourly health check now tries force-kill (keeps auth, no QR rescan) before falling back to delete+recreate for failed sessions
- Duplicate message bug — notification flow with dynamic header sent duplicate messages
- Broken test imports — corrected import paths and frappe cache parameter names
-
RedisWrapper.set_value()only_setunsupported — changed toget_value+set_valuepattern -
UnboundLocalError in outbox processor —
accountreferenced before definition - Template sent as raw Meta dict — stored entire Meta payload instead of rendered text
- Dynamic header skipped template — outbox returned early after image send
-
Jinja sent as raw template — set
templateon doc causing wrong send path -
requestsnot defined — barerequests.post()calls missed during connection pooling migration -
Template vs text send guard — changed from
if self.templatetoif self.use_template and self.template - Jinja send type bypassed OpenWA — fell through to parent's Meta API
-
Empty vars sent to OpenWA — bulk messages with empty
template_variablescaused 500 -
get_account_settingtreats0as falsy — changed toval if val is not None -
time.sleep()blocking workers — reduced polling from 20s to 3s - Outbox race condition — added Redis-based distributed lock
-
frappe.db.commit()anti-pattern — removed explicit commit from inbound webhook -
"Password not found" error — account resolution before
_is_openwa_account()check -
frappe.msgprint()in background — replaced withfrappe.logger().info() - N+1 queries in health check — cached account loading
- Documentation inaccuracies — FLOWS.md, ARCHITECTURE.md, DEPLOYMENT.md, CUSTOM_FIELDS.md corrected
-
send_document_pdfmissing read-permission check — the whitelisted method accepted any caller-supplied document and rendered/sent it later as the worker user; it now rejects the send unless the caller can read the reference document (frappe.has_permission). -
Phantom stats methods removed —
get_overview_stats/get_message_statswere documented and tested but never implemented (the real/api/stats/*routes require an ADMIN + unscoped key the bridge does not hold); removed from tests, docs and the wiki.get_session_stats(GET /api/sessions/stats/overview) remains. -
Multi-chunk bulk total inflation —
send_bulk_openwasummed counts against the full recipient list for every chunk; each chunk is now sized against its own batch. -
Unbounded delivery-failure dedupe scan —
_known_delivery_failure_keysnow filters Event Log rows to the last 30 days. -
scheduled_atstring comparison —after_insertnormalizes a possible stringscheduled_atviafrappe.utils.get_datetimebefore comparing. -
Statistics / setup docs corrected — Statistics section documents the real unscoped
/api/stats/*routes;setup_openwa_sessionexample shows the actualqr_ready/readycontract.
-
OpenWA v0.19-v0.23 API contract support — bridge follows the OpenWA
v0.23.7API surface:-
send_catalog_messagedeprecated —POST /messages/send-catalogwas removed in v0.19 and answers 501 on every engine; the method now returns a clear error pointing tosend_product_message(product card, Baileys) /send_product_to_chat/send_catalog_to_chat(text+image / text-summary fallbacks) instead of calling the dead endpoint. -
mark_chat_readaccepts an optionalmessage_idsargument — comma-separated list forwarded as the v0.23messageIdsarray (max 100, Baileys) for per-message read receipts; omitted → the whole chat is marked read as before. - New message endpoints:
vote_poll(POST /messages/vote-poll),pin_message(POST /messages/pin, validateddurationSeconds86400/604800/2592000),unpin_message(POST /messages/unpin),star_message(POST /messages/star),get_chat_media(GET /messages/:chatId/:messageId/media). - New chat/session endpoints:
archive_chat,mute_chat,pin_chat(POST /chats/archive|mute|pin),clear_chat_messages(DELETE /chats/:chatId/messages),get_session_proxy/set_session_proxy(per-session egress proxyGET/PATCH /proxy). - New group endpoints:
get_group_membership_requests,approve_group_membership_requests,reject_group_membership_requests(GET/POST /groups/:groupId/membership-requests[/approve|/reject]; empty participant list approves/rejects all). - New status endpoint:
post_status_voice(POST /status/send-voice, URL or base64). - New channel endpoints:
create_channel(POST /channels),mute_channel(POST /channels/:channelId/mute),demote_channel_admin(POST /channels/:channelId/admins/demote),transfer_channel_ownership(POST /channels/:channelId/owner/transfer). - The v0.19 uniform
{sessionId}route change requires no bridge change — relative paths are appended to/api/sessions/{session_id}byopenwa_api().
-
-
OpenWA v0.23.5-v0.23.7 refinements:
- Inbound commerce messages (0.23.5) — OpenWA now types
order/productonmessage.receivedinstead of bodylessunknown;openwa_type_to_frappemaps both tocontent_type = "order". - Inbound prompt taps (0.23.6+) — business-prompt button/list replies arrive as
type: "text"with abutton { id, text? }field;_handle_inbound_messagestores the tapped button text (or id) as the message body when the body is empty. - New
click_buttonmethod (0.23.6) —POST /messages/click-buttonsends a structured reply against a stored prompt (Baileys; whatsapp-web.js answers 501). - Catalog docs corrected — OpenWA exposes read-only catalog endpoints plus
send-product; the fallback-product path is now described accurately.
- Inbound commerce messages (0.23.5) — OpenWA now types
-
OpenWA v0.24-v0.25 API contract support — bridge tracks the current OpenWA
main(v0.25.0):- Outbound single-recipient sends carry an
Idempotency-Keyheader (v0.25) — see Added. OpenWA replays the stored response within 24 h instead of sending twice and returnsIdempotent-Replayed: trueon replay. -
message.ack/message.failedpayloads carrychatId(v0.25) —_handle_status_updatereads it and_find_whatsapp_message()uses it as a fallback for messages whose storedtois blank. - New opt-in
message.receiptwebhook event (v0.25) — see Added. - Webhook receivers must deduplicate on
X-OpenWA-Idempotency-Key(v0.25 upgrade note) — the inbound endpoint already reads it.
- Outbound single-recipient sends carry an
- Settings moved to WhatsApp Account — 7 settings moved from global to per-account
- WhatsApp Account custom fields — expanded from 9 to 16 fields
- OpenWA Bridge Settings — reduced from 10 to 3 fields (only global settings remain)
-
All
requests.get/postcalls — replaced with_http_sessionfor connection pooling -
All
get_password("openwa_api_key")calls — replaced withget_api_key()helper -
All
_is_openwa_account()definitions — consolidated to single function in utils.py
- Initial release
- OpenWA integration via
override_doctype_class - Outbound message routing (text, image, video, audio, document, location, contact, poll, reaction, reply, template)
- Inbound message reception via webhook
- Template sync to OpenWA
- Notification system (Jinja, Template, Dynamic Image Header)
- QR code setup flow
- Auto-reconnect scheduled tasks