Releases: aldinokemal/go-whatsapp-web-multidevice
Release list
v9.4.0
What's New in v9.4.0
🔄 On-Demand Chat History, Per-Device Webhook Fan-Out & Status Media Control
Version 9.4.0 lets you pull older chat history from the phone on demand, allows a device's own webhook to add to the global targets instead of replacing them, and adds a switch to skip status media downloads. History sync naming, device purge, webhook delivery, and route ordering all get reliability fixes, and whatsmeow is updated to the latest revision.
✨ Features
On-Demand Chat History (#834)
- Adds
POST /chat/{chat_jid}/historyto request older messages from the phone, anchored at the oldest message currently stored locally for that chat - Accepts a
countof older messages to request and returns the requested count, with percent-encoded chat JIDs accepted on the path - Documents the endpoint in OpenAPI and rejects requests for a chat with no stored message to anchor on
Per-Device Webhooks Can Add to the Global Targets (#842)
- Adds
--webhook-device-merge-global/WHATSAPP_WEBHOOK_DEVICE_MERGE_GLOBALso a device with its own webhook also delivers its events to the global--webhookURLs - Leaves the default unchanged: a per-device webhook still replaces the global ones
- Documents the flag in the README,
.env.example, and the webhook payload reference
Ignore Status Media Downloads (#836)
- Adds
--ignore-status-media/WHATSAPP_IGNORE_STATUS_MEDIAto skip downloading media attached to status broadcasts - Keeps status events flowing while avoiding the storage and bandwidth cost of their media
🐛 Bug Fixes
History Sync Chat Naming (#845)
- Names 1:1 chats from the contact store instead of the history-sync
DisplayName - Keeps
DisplayNamefor group and newsletter chats, where it is the authoritative title - Preserves the disappearing-messages timer when a chat is refreshed by an on-demand sync
Device Purge and Path Decoding (#843)
- Percent-decodes the
:device_idpath parameter so clients can safely sendencodeURIComponentvalues - Resolves purge targets verbatim instead of trimming, so distinct slots are never confused, and keeps the registry entry when local cleanup fails so
DELETEcan be retried - Skips the keep-slot callback on a purge-triggered logout, purges both the registry id and the paired JID partition when they differ, and broadcasts
DEVICE_REMOVEDwith the refreshed device list - Surfaces shared-JID lookup failures instead of silently continuing when companion slots share one number
Chatwoot Config Routes and Device Route Order (#837)
- Registers the Chatwoot config routes before the header-based device group so
DeviceMiddlewareno longer intercepts them - Keeps the device group as the last registration on the API group —
Group("")installs a root handler matching every path — and pins the order with a regression test - Hardens the status-media guard to match on the JID's shape, so a device-qualified status JID is still recognised
Slow Device Webhook No Longer Starves the Global Leg
- Runs the device and global webhook legs concurrently so one slow endpoint burning its retry backoff cannot leave the global leg a dead context
- Dedupes a global URL against the device URLs only when the device leg will actually deliver, so an event rejected by the device's own filter still reaches the global subscription
🔧 Technical Improvements
Dependency & Documentation Updates
- Updates whatsmeow to the latest revision
- Streamlines the AGENTS documentation across chatstorage, chatwoot, whatsapp, ui, usecase, and validations
What's Changed
- chore: update whatsmeow to latest by @aldinokemal
- refactor: streamline AGENTS documentation and improve clarity across multiple sections by @aldinokemal
- fix(history-sync): name 1:1 chats from the contact store, not DisplayName (#845) by @arivelli
- fix(device): make DELETE purge reliable and decode path device_id (#843) by @hanlest
- feat(webhook): let a per-device webhook add to the global targets instead of replacing them (#842) by @Kizuno18
- fix(device): compare purge slot id verbatim and surface shared-JID lookup failures by @aldinokemal
- fix(webhook): stop a slow device webhook starving the global leg by @aldinokemal
- fix(history-sync): keep DisplayName for group and newsletter chats by @aldinokemal
- fix(rest): register chatwoot config routes before DeviceMiddleware (#837) by @aguinaldotupy
- feat: add flag to ignore downloading status media (#836) by @danihidayatx
- feat(chat): add on-demand history sync endpoint (#834) by @Kizuno18
- fix(history-sync): keep the disappearing-messages timer on on-demand sync by @aldinokemal
- fix(rest): keep the device group last, and harden the status-media guard by @aldinokemal
- feat: update SKILL by @aldinokemal
- chore(release): v9.4.0 by @aldinokemal
Contributors
Full Changelog: v9.3.1...v9.4.0
v9.3.1
What's New in v9.3.1
🔒 Chatwoot Public API Path Escaping & Whatsmeow Updates
Version 9.3.1 hardens Chatwoot public API requests by escaping path segments correctly and updates whatsmeow through the latest revisions.
🐛 Bug Fixes
Escape Chatwoot Public API Path Segments (#819)
- Escapes public API path segments before constructing Chatwoot requests
- Prevents malformed paths when account, inbox, contact, or conversation identifiers contain reserved characters
🔧 Technical Improvements
Dependency Updates
- Updates whatsmeow to the latest revision
What's Changed
- fix(chatwoot): escape public API path segments (#819) by @aldinokemal
- chore: update whatsmeow to latest by @aldinokemal
- chore(release): v9.3.1 by @aldinokemal
Contributors
Full Changelog: v9.3.0...v9.3.1
v9.3.0
What's New in v9.3.0
📬 Newsletter Media Downloads, Poll Vote Webhooks & Audio Played Receipts
Version 9.3.0 adds newsletter media downloads by server ID, exposes decrypted poll vote selections in webhooks, and introduces played receipts for incoming audio and voice notes. Group read receipts now use the original message sender, and whatsmeow is updated to the latest revision.
✨ Features
Newsletter Media Download (#818)
- Adds
GET /newsletter/messages/{server_id}/download?newsletter_id=...to download image, video, video-note, audio, document, and sticker media from newsletters - Returns local file metadata and a base-path-aware public URL without exposing WhatsApp media secrets
- Documents the endpoint in OpenAPI and covers validation, target selection, media selection, and REST behavior with regression tests
Poll Vote Selections in Webhooks (#816)
- Decrypts poll vote updates with the pinned whatsmeow client and persists device- and chat-scoped poll definitions from live messages, sent polls, and history sync
- Returns selected option names, lowercase hashes, full option lists, and explicit resolution states while preserving
event: "message"compatibility - Handles current poll creation variants plus poll edit/add-option updates, and degrades safely when definitions or original message secrets are unavailable
Audio Played Receipts (#813)
- Adds
POST /message/:message_id/playedto send WhatsApp's played receipt for incoming audio and voice-note messages - Resolves messages through device-scoped chat storage, verifies the requested chat and audio direction/type, and uses the original participant JID for group receipts
- Adds the equivalent MCP
mark_playedaction and documents the REST endpoint in OpenAPI
🐛 Bug Fixes
Group Read Receipts Use Original Sender (#814)
- Uses the stored original group message sender for
MarkAsReadinstead of the active device JID, so incoming group read receipts reach WhatsApp correctly - Resolves group messages by their full storage identity
(device_id, chat_jid, message_id)and normalizes stored sender JIDs, including@lidto PN when a mapping is available - Rejects missing or non-user group senders instead of falling back to the local device JID, while preserving existing direct-chat behavior without requiring chat storage
🔧 Technical Improvements
Dependency & Documentation Updates
- Updates whatsmeow to the latest revision
- Syncs the README with the current server capabilities and configuration
What's Changed
- feat(message): add audio played receipts (#813) by @aldinokemal
- docs: sync README with current server by @aldinokemal
- fix(message): use original group sender for read receipts (#814) by @aldinokemal
- feat(webhook): expose poll vote selections (#816) by @aldinokemal
- chore: update whatsmeow to latest by @aldinokemal
- feat(newsletter): add media download endpoint (#818) by @aldinokemal
- chore(release): v9.3.0 by @aldinokemal
Contributors
Full Changelog: v9.2.2...v9.3.0
v9.2.2
What's New in v9.2.2
🔐 Basic Auth Prompt Restored with MCP OAuth Enabled
Version 9.2.2 is a focused patch release that restores the browser Basic Auth prompt for the UI and REST API when MCP OAuth 2.1 is enabled. OAuth and Basic authentication for the MCP endpoint continue to work as before.
🐛 Bug Fixes
OAuth Middleware Scoped to MCP
- Limits the OAuth-or-Basic authentication challenge to the
/mcptransport instead of intercepting unrelated routes registered later - Restores the standard Basic Auth challenge for the UI, REST API, and favicon requests, allowing browsers to display their native credential prompt
- Preserves both OAuth Bearer token and existing Basic Auth access to MCP
- Adds regression coverage for root deployments and installations using
APP_BASE_PATH
What's Changed
- fix(auth): scope OAuth middleware to MCP by @aldinokemal
- chore(release): update
AppVersiontov9.2.2by @aldinokemal
Contributors
Full Changelog: v9.2.1...v9.2.2
v9.2.1
What's New in v9.2.1
🐛 Accurate Chat Names, Per-Device Pagination & Reliable Message Deletes
Version 9.2.1 is a patch release focused on chat and message correctness. Revoked and deleted messages are now removed from local chat storage instead of lingering in the UI and API, message pagination counts are scoped to the requesting device, and chat labels are resolved from the synced WhatsApp address book instead of falling back to raw phone numbers. The CI workflows are also refreshed to the current GitHub Actions majors.
🐛 Bug Fixes
Revokes and Deletes Sync to Chat Storage (#810)
- Removes the message from local chat storage after a successful revoke or delete, so the chat API and UI no longer return messages that no longer exist on WhatsApp
- Applies the same cleanup to incoming revoke events, so messages revoked from another device also disappear locally
- Makes message deletion atomic: reactions, edits, and the message row are removed in a single transaction and roll back together on failure
- Preserves Chatwoot message links on delete so asynchronous delete forwarding can still resolve them
- Returns an explicit error when the device ID or chat storage is unavailable instead of silently leaving stale rows behind
Message Pagination Counted Per Device (#811)
- Scopes the total message count in
GET /chat/{chat_jid}/messagesto the requesting device - Fixes inflated totals and page counts on multi-device setups where the same chat JID exists for more than one connected device
Display Names Resolved from Synced Contacts (#812)
- Resolves chat labels from the address book synced by whatsmeow, falling through
FullName,PushName, andBusinessNamebefore the JID-derived label - Replaces phone-number and JID placeholders in chat list and chat detail responses with real contact names
- Normalizes LID identifiers through the active device mapping, including pre-normalization placeholders and late LID mappings
- Uses point lookups for single-chat requests and a single contact snapshot for list requests, avoiding N+1 reads and eager full contact scans
- Leaves group subjects, newsletter names, and
status@broadcasthandling unchanged; contact store failures stay non-fatal and fall back to the existing deterministic label
🔧 Technical Improvements
CI Toolchain Refresh
- Bumps
actions/checkoutv4 → v7,actions/setup-gov5 → v7,docker/login-actionv3 → v4,docker/setup-buildx-actionv3 → v4,docker/setup-qemu-actionv3 → v4, anddocker/build-push-actionv6 → v7 - Keeps the pinned Go version in sync with
src/go.mod(1.26.0), whichsetup-gov6+ requires because it forcesGOTOOLCHAIN=local - Aligns the stale Go version reference in
AGENTS.mdwithgo.mod
What's Changed
- fix(message): sync deletes to chat storage (#810) by @aldinokemal
- fix(chat): scope message pagination count by device (#811) by @aldinokemal
- fix(chat): resolve display names from synced contacts (#812) by @aldinokemal
- chore(ci): bump GitHub Actions to latest major versions by @aldinokemal
- chore(release): update
AppVersiontov9.2.1by @aldinokemal
Contributors
Full Changelog: v9.2.0...v9.2.1
v9.2.0
What's New in v9.2.0
🔐 Embedded MCP OAuth 2.1, HD Media & More Reliable Releases
Version 9.2.0 adds an opt-in OAuth 2.1 authorization server directly to the existing MCP endpoint, allowing remote MCP clients such as Claude custom connectors to authenticate without custom Basic Auth headers. It also adds explicit HD quality for photos and videos, fixes per-device Chatwoot webhook URLs that contain query parameters, and aligns the release and Docker toolchains with Go 1.26.
✨ New Features
Embedded OAuth 2.1 for MCP (#802)
- Adds an opt-in authorization-code flow with mandatory PKCE
S256for the streamable-HTTP MCP endpoint - Supports OAuth Bearer tokens alongside the existing Basic Auth credentials while leaving normal REST and UI authentication unchanged
- Implements protected-resource and authorization-server metadata, dynamic client registration, resource binding, refresh-token rotation, and token-family revocation on reuse
- Stores only hashes of opaque authorization codes and tokens in a dedicated SQLite database
- Adds configuration and deployment guidance for Claude custom connectors, reverse proxies,
APP_BASE_PATH, and Cloudflare - Remains disabled by default with
MCP_OAUTH_ENABLED=false
HD Photo and Video Sending (#809)
- Adds an explicit
hdoption to REST and MCP image/video send requests - Sends photos at up to a 2560px maximum edge with JPEG quality 92, without upscaling smaller images
- Sends videos with an aspect-preserving H.264 CRF 23 profile capped inside 1280x1280
- Populates image and video dimensions plus video duration in outgoing WhatsApp metadata
- Keeps existing compression defaults unchanged;
hd=truetakes precedence overcompress
🐛 Bug Fixes
Chatwoot Webhook URLs Preserve Query Parameters (#801)
- Appends the device ID to the webhook path without corrupting existing query parameters
- Fixes URLs such as
...?secret=value/device-idby producing the correct.../device-id?secret=valueform - Keeps the existing relative
APP_BASE_PATHfallback unchanged
🔧 Technical Improvements
Go 1.26 and Release Reliability
- Raises the minimum Go version to 1.26.0 for current dependency requirements
- Aligns Docker and GoReleaser builds with Go 1.26
- Prevents multi-architecture Docker publishing from failing during
go mod downloadbecause of a stale builder toolchain - Strengthens the guarded release workflow so versioning, tests, commit, main push, tagging, and release verification follow one confirmed sequence
Dependencies
- Refreshes
go.mau.fi/whatsmeowandgo.mau.fi/utilto their latest snapshots included in this release
What's Changed
- fix(chatwoot): preserve query params in per-device webhook URL (#801) by @aldinokemal
- feat(mcp): add embedded OAuth 2.1 authorization (#802) by @aldinokemal
- feat(send): add HD photo and video quality (#809) by @aldinokemal
- chore: update
go.mau.fi/whatsmeowto latest snapshots by @aldinokemal - refactor(new-release): automate guarded version, commit, push, and tag flow by @aldinokemal
- chore(release): update
AppVersiontov9.2.0by @aldinokemal - fix(ci): align Docker and release builds with Go 1.26 by @aldinokemal
Contributors
Full Changelog: v9.1.0...v9.2.0
v9.1.0
What's New in v9.1.0
🤖 Unified MCP, Reliable Multi-Device Chatwoot & Interactive Messages
Version 9.1.0 brings MCP into the REST server, so API consumers and AI agents share one process, one WhatsApp session/device registry, one port, and one authentication boundary. It also fixes per-device Chatwoot forwarding and makes native WhatsApp interactive messages readable in Chatwoot.
⚠️ Breaking Changes
MCP Runs Through rest
- The standalone
mcpcommand is no longer available - Start both REST API and MCP with
./whatsapp rest - Connect MCP clients to
http://<host>:<port>/mcp(default:http://localhost:3000/mcp) - Existing REST basic authentication also protects the MCP endpoint
- The previous granular MCP surface is consolidated into five tools:
whatsapp_send,whatsapp_message,whatsapp_chat,whatsapp_group, andwhatsapp_app
Migration
./whatsapp mcp→./whatsapp resthttp://localhost:8080/sse→http://localhost:3000/mcp- MCP uses streamable HTTP transport at
/mcp - Provide
device_idper MCP call when needed, or select a device for the connection withX-Device-Id
✨ New Features
Unified MCP Endpoint & Consolidated Tools (#788)
- Serves MCP from the existing REST process at
/mcp, sharing the same WhatsApp lifecycle and device state - Consolidates the public MCP surface into five action-based tools
- Supports explicit
device_id,X-Device-Id, and default-device resolution for MCP calls - Validates action-specific MCP input schemas before handlers execute
- Restricts MCP routing to supported POST/DELETE operations, avoiding unsupported GET streaming requests
Chatwoot Supports WhatsApp Interactive Messages (#794)
- Renders native WhatsApp CTA buttons and interactive messages as useful plain text in Chatwoot
- Includes labels and the URL, phone number, or copy code for supported button types
- Preserves a readable fallback for interactive button types that cannot be reduced to one line
🐛 Bug Fixes
Per-Device Chatwoot Forwarding Keeps Device Context (#793)
- Preserves the WhatsApp device context in asynchronous webhook and Chatwoot forwarding work
- Fixes live events silently skipping the configured per-device Chatwoot inbox
- Covers messages, receipts, deletes, groups, presence, calls, newsletters, and label app-state events
Cleaner Chatwoot Reaction Notes (#795)
- Removes redundant raw WhatsApp message IDs from reaction notes
- Keeps replies threaded to the reacted-to Chatwoot message as before
Unstar Handles Explicit false
- Accepts
is_starred: falsecorrectly through both REST and MCP message actions
🔧 Technical Improvements
Release Safety & Metadata
- Adds a guarded release workflow skill that checks the exact app version, clean/synced
main, tag/release availability, and tests before a tag push - Updates the application version to
v9.1.0so runtime metadata matches this release
Dependencies & Documentation
- Refreshes
go.mau.fi/whatsmeowthrough the three dependency updates included in this release - Adds an explicit v9 Breaking Changes entry to the README
- Clarifies that MCP is an endpoint of the REST server, not a separate process
What's Changed
- feat: unify MCP endpoint and consolidate tools (#788) by @aldinokemal
- feat(chatwoot): render InteractiveMessage (native CTA buttons) as text (#794) by @Claudi0-Oliveira
- fix(webhook): propagate device context to async forward goroutines (#793) by @Claudi0-Oliveira
- fix(chatwoot): drop redundant WhatsApp message ID from reaction notes (#795) by @Claudi0-Oliveira
- chore: update
go.mau.fi/whatsmeowto latest snapshots by @aldinokemal - feat: add guarded new release skill by @aldinokemal
- feat: increase version to
v9.1.0by @aldinokemal - docs: clarify v9 MCP REST unification by @aldinokemal
Contributors
New Contributors
- @Claudi0-Oliveira made their first contribution in #793
Full Changelog: v9.0.1...v9.1.0
v9.0.1
What's New in v9.0.1
🛠️ Maintenance Release: Sender Names, Reliable Webhooks & URL Fixes
This release adds sender display names across chat history, reactions, and webhook payloads, while fixing several reliability issues around generated URLs, device webhook setup, webhook delivery, concurrent image sends, and LID JID formatting. It also improves diagnostics for undecryptable messages, documents device pairing responses more clearly, and refreshes WhatsApp protocol dependencies.
✨ New Features
Sender Display Names (#782)
- Adds
sender_display_nameto chat history messages, nested reactions, and sender-bearing webhook payloads - Resolves names dynamically using the best available contact information while preserving the existing raw sender fields and legacy
from_namebehavior - Uses per-response caching to avoid repeated lookups and requires no database migration or backfill
🐛 Bug Fixes
Generated URLs Preserve Non-Default Ports (#781)
- QR-code and downloaded-media URLs now retain the request port when the service is accessed through a non-default port
- Replaces Fiber's hostname-only value with the complete request host while preserving existing proxy behavior
Webhook Configuration Uses the Generated Device ID (#785)
- Creating a device without supplying
device_idnow saves its webhook configuration against the ID generated for the new device - Prevents a misleading 500 response after the device slot was already created and ensures the requested webhook settings are persisted
Webhook Requests Include Content-Length
- Webhook bodies are now attached when the HTTP request is created, producing a
Content-Lengthheader instead of chunked transfer encoding - Fixes empty request bodies on receivers that do not reliably expose chunked payloads, including some PHP deployments behind nginx/FPM
- Retry attempts now replay the original request body safely
Concurrent Image Sends Use Unique Temporary Files
- Temporary image files are prefixed with a UUID so simultaneous sends of identically named files cannot overwrite or delete each other's working files
LID JIDs Drop Device Suffixes
FormatJIDnow normalizes device-specific suffixes for@lidaddresses as well as standard WhatsApp JIDs
🔧 Technical Improvements
Undecryptable Message Diagnostics
- Undecryptable incoming messages are now logged at warning level with sender and failure details instead of being silently dropped
- No webhook or stored message is emitted because these events contain no plaintext payload
Device Login OpenAPI Documentation
- Clarifies the device QR and pairing-code login endpoint descriptions and error responses
- Adds explicit success schemas for device login and pairing-code responses
Dependency & Documentation Updates
- Refreshes
go.mau.fi/whatsmeowto recent snapshots throughout the release cycle - Corrects a stale configuration comment around
viper.IsSet
What's Changed
- fix: send webhooks with Content-Length instead of chunked by @aldinokemal
- fix: strip device suffix from
@lidJIDs inFormatJIDby @aldinokemal - fix: log undecryptable messages instead of dropping them silently by @aldinokemal
- fix: prefix send-image temp files with a UUID by @aldinokemal
- docs: correct stale
viper.IsSetcomment by @aldinokemal - fix: keep the request port in generated URLs (#779) by @aldinokemal in #781
- feat: add sender display names by @aldinokemal in #782
- feat: update device login endpoints in OpenAPI spec by @aldinokemal
- fix: save device webhook config against the generated device ID by @alkadoHs in #785
- chore: update whatsmeow to latest by @aldinokemal
New Contributors
Full Changelog: v9.0.0...v9.0.1
v9.0.0
What's New in v9.0.0
🚀 Major Release: Standalone gowa-ui Dashboard, Pure API Backend & Fiber v3
This release splits the web dashboard out of the binary. gowa is now a pure API server that downloads the latest gowa-ui release at runtime, verifies it, caches it, and serves it at / — same URL, same basic auth. The second breaking change is the move to Fiber v3, which reshapes the Go-level API for anyone embedding gowa as a library while leaving HTTP consumers untouched. This release also adds GET /app/info and cross-origin WebSocket auth so standalone UIs can talk to the server, and fixes sent messages being lost under SQLite write contention plus percent-encoded chat_jid path params never matching.
⚠️ Breaking Changes
Embedded Dashboard Removed (#766)
src/views/(56 Vue components), thego:embedbundle, the HTML template engine, and the basic-auth token echo middleware are gone; the dashboard now lives in its own repo — aldinokemal/gowa-ui (React 19 + Vite + Tailwind 4 + shadcn/ui), released as a single self-containedgowa-ui.html- The server fetches that asset at runtime and serves it at
/— same URL, same basic auth, so a normal browser-based deployment behaves as before after the first download APP_UI_ENABLED=falseturns/into a JSON banner for pure-API deployments- Air-gapped installs: set
APP_UI_AUTO_UPDATE=falseand pre-seedstorages/ui/, or disable the UI entirely
Fiber v3 Upgrade (#764)
- Fiber core
v2.52.14→v3.4.0, GoFiber utilities tov2.1.2, and the WebSocket add-on fromgithub.com/gofiber/websocket/v2togithub.com/gofiber/contrib/v3/websocket v1.2.1 - Relevant if you embed
gowaas a library or build against its handlers: Fiber v3 changes the context, binding, static middleware, configuration, testing, and utilities APIs - The whole Fiber-dependent surface was migrated in one pass — REST handlers, middleware, request binding, query parsing, context propagation, trusted-proxy/listen configuration, static delivery, and tests — so no mixed v2/v3 behavior is retained
- HTTP API consumers are unaffected: routes, payloads, device scoping, and WebSocket behavior are preserved, the plaintext
APP_BASIC_AUTH=user:secretcontract is kept via a constant-time custom authorizer, and generated QR/media URLs stay correct through the Fiber v3 request scheme API
✨ New Features
Runtime-Downloaded Dashboard (#766)
- New
src/infrastructure/uiassetfetches the latestgowa-ui.htmlrelease asset ofAPP_UI_REPO(defaultaldinokemal/gowa-ui), compares sha256 digests, uses ETag-conditional requests, and writes an atomic cache atstorages/ui/ - Serves the cached asset with ETag/304, auto-updating every
APP_UI_UPDATE_INTERVAL(default3h, ±10% jitter), and falls back to a graceful offline page when the cache is empty and GitHub is unreachable - Config:
APP_UI_ENABLED,APP_UI_AUTO_UPDATE,APP_UI_REPO,APP_UI_ASSET_NAME,APP_UI_UPDATE_INTERVAL,APP_UI_GITHUB_TOKEN(optional, raises the GitHub API rate limit) APP_UI_ASSET_SHA256is an operator-supplied supply-chain pin: when set, the server refuses to download or serve any asset whose sha256 differs — checked against the release digest before download, against the downloaded bytes, and against the cache on boot
Server Metadata Endpoint (#766)
- Adds
GET /app/inforeturning{version, os, base_path, max_file_size, max_video_size, max_image_size, chatwoot_enabled}, replacing the HTML template injection that standalone UIs can no longer rely on
Cross-Origin WebSocket Auth & CORS (#766)
- Browsers cannot set headers on a WebSocket handshake, so
/ws?authorization=<base64(user:pass)>is now accepted and validated by the same constant-time basic-auth authorizer — TLS is required in production - CORS is widened and moved ahead of statics:
AuthorizationandX-Device-Idare allowed with explicit methods, and QR PNGs plus media under/staticsnow carry CORS headers
🐛 Bug Fixes
Sent Messages Lost Under Write Contention (#766)
wrapSendMessagestored sent messages in a detached goroutine with a 2s budget while SQLite'sbusy_timeoutis 30s, so during history-sync contention the deadline could expire between the chat-bump write and the message insert — leaving a bumped chat with a missing message- The message row is now written first (a partial failure loses only the invisible timestamp bump), the budget is raised to 15s, and failure logs carry the message ID and recipient
Percent-Encoded chat_jid Never Matched (#766)
- Fiber does not URL-decode path params, so any client that percent-encodes the JID path segment (gowa-ui and most HTTP libraries) got empty results from
/chat/{chat_jid}/*even though the rows existed - All four chat handlers now
url.PathUnescapethe param — a raw@is unchanged, so existing clients see no behavior change — and malformed escapes return the standard 400 JSON envelope - OpenAPI notes added on all four
chat_jidparams
Contact Display Names in Listings
MyListContactsreturned onlyFullName, so contacts saved without one came back with an empty name- Contact listings now apply the same saved → push → business name precedence used elsewhere, with unit test coverage for the fallback order
🔧 Technical Improvements
Dependency Updates
go.mau.fi/whatsmeow: refreshed to the latest snapshot (v0.0.0-20260718134955-fac667d55293)- Fiber and its add-ons moved to v3 — see Breaking Changes above for the full list
- Dropped
github.com/gofiber/template/html/v2along with the template engine;go.mau.fi/utiland assorted indirect dependencies refreshed
Cleanup
- Removed dead Chatwoot client code and its now-redundant test scaffolding, a stale error constant, and obsolete planning docs
- Logo moved to
gallery/gowa.svg; obsolete dashboard screenshots removed from the gallery
Release Metadata
AppVersionis nowv9.0.0, so runtime status matches the published release tag
What's Changed
- chore: upgrade Fiber to v3 by @aldinokemal in #764
- feat!: standalone gowa-ui dashboard — pure API backend + runtime-downloaded UI by @aldinokemal in #766
- chore!: bump version to v9.0.0 by @aldinokemal
- chore: remove deadcode by @aldinokemal
- chore(gallery): remove obsolete dashboard screenshots by @aldinokemal
- fix(contacts): use fallback names in contact listings by @aldinokemal
- chore: update whatsmeow to latest by @aldinokemal
Full Changelog: v8.11.0...v9.0.0
v8.11.0
What's New in v8.11.0
🚀 Minor Release: Forward-by-ID, Outbound Proxy, Chatwoot Multi-Device Routing & History-Sync Group Fixes
This release adds forward-a-message-by-ID, an outbound WHATSAPP_PROXY for the WhatsApp WebSocket, a per-JID webhook ignore list, and full Chatwoot multi-device / multi-inbox routing. It also hardens the multi-device lifecycle by keying companion sessions on the full AD JID, recovers inbound group history that was previously dropped during history sync, and fixes Chatwoot group replies plus link-message storage.
✨ New Features
Forward Message by ID (#755)
- Adds
POST /message/{message_id}/forwardto forward a message already in local chat storage to another chat, with destinationphone, optionalduration, andforce_reupload - Rebuilds the whatsmeow protos from SQLite metadata (
BuildForwardMessageFromStorage), sets forwarded context (IsForwarded,ForwardingScore), and falls back to download + re-upload when media references are stale - v1 supports text and standard media (image, video, video note, audio, document, sticker); contact/location/poll/link/call return an explicit unsupported-type error
- Also exposed via the MCP tool
whatsapp_forward_message, OpenAPI docs, and the embedded UI (SendForward.js)
Outbound Proxy Support (#664)
- Adds
WHATSAPP_PROXY(SOCKS5 / HTTP / HTTPS) to route the WhatsApp WebSocket through an outbound proxy via whatsmeow'sSetProxyAddress - Standard
HTTP_PROXY/HTTPS_PROXYdo not apply to the WebSocket dialer, so this unblocks deployments behind DPI, corporate egress restrictions, or datacenter-ASN reputation issues - Wired into both the legacy single-client path and the multi-device path; empty/unset keeps the current direct-connection behavior (100% backward-compatible), and a bad URL logs a warning without aborting startup
Webhook JID Ignore List (#736)
- Adds
WHATSAPP_WEBHOOK_IGNORE_JIDS(and--webhook-ignore-jids), mirroringCHATWOOT_IGNORE_JIDS, to drop specific chats/JIDs from the generic webhook — e.g.@g.usmutes all group traffic - Supports the
@g.us/@s.whatsapp.net/@lidaddress-space wildcards and exact JIDs, matching the event'schat_idorfrom - Orthogonal to
WHATSAPP_WEBHOOK_EVENTS: an event forwards only if its type is allowed and its JID is not ignored; the default (no list) forwards unchanged
Chatwoot Multi-Device / Multi-Inbox Routing (#710)
- Routes each WhatsApp device to its own Chatwoot destination (URL + account + inbox + token) in both directions, configurable at runtime via REST
- New
ClientRegistryreplaces the Chatwoot singleton;CHATWOOT_*env is used only while thechatwoot_device_configstable is empty, then switches to fail-fast (unmapped devices are skipped on forward / error on reverse rather than silently using the global inbox) - Adds
POST /chatwoot/webhook/:device_id(route-by-config, validates payload account/inbox against the device config), account-scoped conversation lookup, echo-dedup partitioned by(accountID, messageID), and per-device sync services - REST CRUD:
GET /chatwoot/configs,GET/PUT/DELETE /devices/:device_id/chatwoot/config— API token masked on read and never logged, routing-identity edits blocked with 409 once links exist chatwoot_urlis validated (http(s) only, no embedded credentials, SSRF guard rejecting private/loopback/link-local/metadata addresses) with a connect-time re-check closing the DNS-rebinding window;CHATWOOT_ALLOWED_HOSTSis the escape hatch for trusted internal Chatwoot
Per-Device Login Endpoints
POST /devices/:device_id/loginand/devices/:device_id/login/codenow delegate to the app login usecase instead of returning not-implemented stubs; the QR handler returnsqr_linkandqr_durationlike/app/login
Chat Composer & Auto-Scroll
- Embedded chat UI gains a message composer and an auto-scroll refresh
🐛 Bug Fixes
Device Key Slot↔Companion Mapping by Full AD JID (#762)
- Two device slots on the same phone number are distinct companion sessions, but the multi-device lifecycle keyed the slot↔companion mapping by the bare-number JID, making siblings indistinguishable — causing session hijack, registry data loss (a legitimate second slot deleted on every boot), and reconnect churn / wrong-row deletes
- Tracks the full AD JID as the slot's companion identity via a new nullable
ad_jidcolumn (migration 35), mirrored fromStore.ID, persisted on connect, and backfilled at boot for older records - Lookups resolve by exact AD JID; a bare-number fallback resolves only when exactly one row matches (never guesses among siblings), and boot reconciliation no longer deletes ambiguous registry records — it logs and skips them
- Single-slot installs keep working unchanged; ambiguous legacy multi-slot state is logged instead of guessed
Inbound Group Messages Dropped During History Sync (#763)
- History sync read the group sender only from
key.participant, which is empty for synced group messages, so every inbound group message hit the "no participant info" branch and was discarded — a paired/re-paired device recovered only its own outbound group history - Falls back to the
WebMessageInfo-level participant (usually a@lid, then resolved viaNormalizeJIDFromLID) before giving up; verified against a real payload (198 skipped → 0, 149 inbound group messages now stored)
Chatwoot Group Message Send Failure (#739)
- Sending to a group chat from Chatwoot failed with "No destination phone for contact" when both the destination attribute and
contact.PhoneNumberwere empty - Falls back to
contact.Identifierso group replies route and send correctly
Send Link Storage Truncation (#661)
POST /send/linkstored a simplified content string that dropped the actual URL when a caption was present- Reuses a single composed text for both the WhatsApp payload and stored message content, so search/history views now show the full link text instead of a caption-only placeholder
🔧 Technical Improvements
Dependency Updates
go.mau.fi/whatsmeow: refreshed to the latest snapshot (v0.0.0-20260713112832-d8960d9575d2)github.com/gofiber/fiber/v2:v2.52.13→v2.52.14;github.com/valyala/fasthttp:v1.71.0→v1.72.0github.com/mark3labs/mcp-go:v0.54.0→v0.56.0modernc.org/sqlite:v1.50.1→v1.53.0;github.com/mattn/go-sqlite3:v1.14.45→v1.14.48- Plus minor bumps to brotli, klauspost/compress, pelletier/go-toml, golang.org/x/image, and others (
go get -u ./...+go mod tidy)
Release Metadata
AppVersionis nowv8.11.0, so runtime status matches the published release tag
What's Changed
- feat(message): add forward message by ID (#394) by @aldinokemal in #755
- fix send link storage by @juliomuhlbauer in #661
- feat(webhook): add WHATSAPP_WEBHOOK_IGNORE_JIDS to skip chats/JIDs (e.g. groups) by @huboperacional in #736
- feat(proxy): outbound WHATSAPP_PROXY env -> whatsmeow SetProxyAddress by @Flow-Mind-Company in #664
- fix(device): key slot↔companion mapping by full AD JID by @aldinokemal in #762
- feat: Chatwoot multi-device / multi-inbox support (#696) by @aldinokemal in #710
- fix: store inbound group messages from history sync (fall back to WebMessageInfo participant) by @nasirnaqash in #763
- fix: failed to send message to group chat by @yokowasis in #739
New Contributors
- @Flow-Mind-Company made their first contribution in #664
- @yokowasis made their first contribution in #739
Full Changelog: v8.10.0...v8.11.0