Releases: aldinokemal/go-whatsapp-web-multidevice
Release list
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
v8.10.0
What's New in v8.10.0
🚀 Minor Release: Passkey Pairing, Per-Device Webhooks, Newsletter Message History & Media Download Fix
This release adds phone-initiated passkey device pairing, per-device webhook URL overrides with global fallback, a REST endpoint to fetch newsletter (WhatsApp channel) message history, and a broad MCP tool expansion for media sends and message manipulation. It also fixes incoming media downloads that failed with "no url present", makes device logout disconnect the session without deleting the device slot, and stops POST /send/file from panicking on non-multipart requests.
✨ New Features
Passkey Pairing Support (#754)
- Adds
GET /app/passkeyto poll a pending WebAuthn challenge or pairing code and its status (none/awaiting_response/awaiting_confirmation) - Adds
POST /app/passkey/responseto submit the signed WebAuthn assertion, andPOST /app/passkey/confirmto confirm the pairing code when the phone's 5-minute handoff window has lapsed - Broadcasts
PASSKEY_REQUEST/PASSKEY_CONFIRMATION/PASSKEY_ERRORover the existing websocket, with minimal web UI toasts - Pending passkey state is stored per device and cleared on pair success, logout, error, or fresh login; the QR login loop no longer logs passkey channel events as errors
Per-Device Webhook URLs (#671)
- Adds
PATCH /devices/{device_id}/webhookandGET /devices/{device_id}/webhookso each device can override the global webhook URL, falling back toWHATSAPP_WEBHOOKwhen unset - Closes gaps found in review: label appstate events now forward correctly on a device-only webhook, a device-webhook lookup failure falls back to global instead of aborting delivery, and
POST /devicesnow acceptswebhook_secret,webhook_events, andwebhook_insecure_skip_verify(previously silently dropped)
Newsletter Message History (#749)
- Adds
GET /newsletter/messages?newsletter_id=...&count=...&before=..., backed by whatsmeow'sGetNewsletterMessages, returningserver_id,message_id,type,timestamp,views_count,reaction_counts, and extractedtext countdefaults to 50 (max 100);beforepages backward by server message id- Rejects non-newsletter JIDs up front with a clear validation error instead of failing deep inside a WhatsApp protocol round-trip
Expanded MCP Send & Message Tools (#722)
- Adds MCP send tools for video, document, audio, and polls
- Adds message-manipulation tools: react, edit, revoke, delete, mark-as-read, and star/unstar, with destructive-hint annotations on revoke/delete
- Send handlers migrated to
mcp-go's typed request helpers (RequireString/GetBool/GetInt/GetStringSlice), matching the existing query tool conventions
🐛 Bug Fixes
Incoming Media Downloads Failing with "no url present" (#722)
- whatsmeow's
Client.Downloadreads onlyDirectPath, never the storedURL, so every reconstructed media message failed to download even though a URL was on file - Derives
DirectPathfrom the already-stored URL at download time (utils.DeriveDirectPath) for both the REST/MCP download path and Chatwoot media sync — no migration or backfill needed, previously-received media is downloadable immediately - Also fixes dropped incoming location messages: adds
FormatLocationSummaryand location/live-location handling to message-text extraction, plus a caption-less extended-text fallback
Device Logout No Longer Deletes the Device Slot (#728)
POST /devices/{id}/logout,GET /app/logout, and a remote unlink from the phone now disconnect the session and clear keys while keeping the device listed, so it can be re-paired under the same id- Deleting a slot entirely is now exclusively
DELETE /devices/{id}, which performs a full purge (logout + WhatsApp unlink + registry removal) instead of leaving orphaned rows - Chat history is preserved on logout and only cleared on delete, matching WhatsApp Web's own behavior; a new
DEVICE_LOGGED_OUTwebsocket event keeps the current UI selection
Non-Multipart POST /send/file Panic (#748)
SendFileunconditionally panicked when no multipart file part was present, unlike the siblingSendImage/SendVideo/SendAudio/SendStickerhandlers, which all guard the lookup- This made the existing
file_urlJSON-body path unreachable dead code; a non-multipart request now falls through to usecase-level validation instead of a 500
Chatwoot Contact Creation on Newsletter JIDs
- WhatsApp channel (
@newsletter) JIDs carry an 18-digit channel id rather than a phone number, so relaying one to Chatwoot failed contact creation with a 422 e164 error - Adds a shared
utils.IsNewsletterJIDguard to both the live webhook-forward path and the history importer
Broadcast Messages Leaking to Webhooks with Chatwoot Enabled
status@broadcastmessages were only skipped from webhook forwarding when Chatwoot was disabled, so enabling Chatwoot let broadcast/status noise reach plain webhook consumers- Broadcast/status messages are now skipped unconditionally;
SetDeviceWebhookConfigalso now returnssql.ErrNoRowsinstead of silently no-oping when the target device doesn't exist
🔧 Technical Improvements
Dependency Updates
go.mau.fi/whatsmeow: refreshed tov0.0.0-20260630180629-b572e5bcb92bgithub.com/vektah/gqlparser/v2: updated tov2.5.36
Documentation
- OpenAPI spec adds the public health endpoint and marks reserved device-login routes as deprecated
- Documents per-device webhook configuration and new label-related webhook events; refreshed README defaults and event list
Release Metadata
AppVersionis nowv8.10.0, so runtime status matches the published release tag
What's Changed
- fix: don't panic on non-multipart POST /send/file requests by @aldinokemal in #748
- feat(newsletter): add endpoint to get latest newsletter messages by @aldinokemal in #749
- fix(media): download incoming media via derived directPath by @nazhiba in #722
- fix(device): logout should disconnect session, not delete device entry by @huboperacional in #728
- feat(app): add passkey pairing support by @aldinokemal in #754
- feat: add per-device webhook support by @mohamedhabibwork in #671
- fix(webhook): skip broadcast messages and return no rows for missing devices by @aldinokemal
- test(utils): cover location summary and extended-text fallback from #722 by @aldinokemal
- refactor(mcp): migrate send handlers to mcp-go request helpers by @aldinokemal
- fix(webhook): close per-device webhook gaps from #671 review by @aldinokemal
- fix(chatwoot): skip @newsletter JIDs before contact creation by @aldinokemal
- docs: update API and webhook documentation by @aldinokemal
- chore: update whatsmeow to latest by @aldinokemal
- chore: bump version to v8.10.0 by @aldinokemal
New Contributors
- @nazhiba made their first contribution in #722
- @huboperacional made their first contribution in #728
- @mohamedhabibwork made their first contribution in #671
Full Changelog: v8.9.0...v8.10.0
v8.9.0
What's New in v8.9.0
🚀 Minor Release: Media Direct-Path Downloads, Selective Call Rejection, SQLite Send Reliability & Empty Chat Polling
This release persists WhatsApp media direct paths so downloads keep working after URL expiry, adds a device-scoped call-reject API and UI, reduces random API send timeouts under heavy incoming message load, and returns empty chat-message responses instead of server errors when a chat row has not been stored yet.
✨ New Features
Selective Call Rejection API (#735)
- Adds
POST /call/rejectfor rejecting a specific incoming call withcaller_jidandcall_id, using the same whatsmeowRejectCallpath as global auto-reject - Keeps existing
WHATSAPP_AUTO_REJECT_CALLbehavior unchanged while enabling per-call business logic from webhook consumers - Adds a device-scoped REST handler, validation, usecase/domain wiring, and a new embedded UI card for manual call rejection
- Documents the endpoint and webhook-driven usage examples in OpenAPI and webhook payload docs
🐛 Bug Fixes
Persisted WhatsApp Media Direct Paths (#731)
- Stores WhatsApp media
direct_pathalongside legacy media URLs in chat storage - Rebuilds downloadable media from the stored direct path, with URL fallback for older rows
- Updates REST downloads, Chatwoot sync, and history-sync reconstruction to use the same downloadable-message helper
- Adds repository and utility coverage for direct-path extraction, persistence, fallback, and media reconstruction
Random API Send Timeouts Under Load (#732)
- Adds
CHAT_STORAGE_MAX_OPEN_CONNSwith a default of 5 so chat storage can use SQLite WAL read concurrency instead of serializing all device, webhook, API, and worker access through one connection - Applies the configured connection count to both open and idle chat-storage connections, clamped to a minimum of 1 for operators who need the previous behavior
- Raises the CGO SQLite busy timeout from 5s to 30s, matching the purego build and reducing
SQLITE_BUSYfailures during write contention - Documents the new environment variable in
src/.env.example
Empty Chat Messages for Missing Chat Rows (#740)
GET /chat/{jid}/messagesnow treats a missing chat row as an empty conversation instead of returning HTTP 500- Returns the normal empty pagination shape with minimal chat info for brand-new or not-yet-upserted chats
- Keeps existing behavior unchanged for chats that are already present in storage
🔧 Technical Improvements
Dependency Updates
go.mau.fi/whatsmeow: refreshed tov0.0.0-20260622185415-5f04eac6dbbbgithub.com/vektah/gqlparser/v2: updated tov2.5.35- Related Go dependency checksums refreshed through
go.mod/go.sum
Release Metadata
AppVersionis nowv8.9.0, so runtime status matches the published release tag
What's Changed
- Persist WhatsApp media direct paths for download support by @aldinokemal in #731
- fix(chat): return empty result instead of 500 when chat row is absent by @huutranluc694-bot in #740
- feat(call): add API endpoint for rejecting incoming calls by @N0N4M3BNS in #735
- Fix: Random API send timeouts under heavy incoming message load by @N0N4M3BNS in #732
- chore: update whatsmeow to latest by @aldinokemal
- chore: bump version to v8.9.0 by @aldinokemal
New Contributors
- @huutranluc694-bot made their first contribution in #740
- @N0N4M3BNS made their first contribution in #735
Full Changelog: v8.8.0...v8.9.0
v8.8.0
What's New in v8.8.0
🚀 Minor Release: Chatwoot Evolution-API Parity, Session-Aware Webhooks, Honest 463 Handling & Media URL Fixes
This release lands a major Chatwoot integration overhaul (auto-provisioning, edits/deletes, conversation reopen, a durable retry queue, and device-scoped routing), adds session correlation to webhooks plus JIDs to app responses, stops masking WhatsApp's server-side 463 reach-out timelock behind a futile client retry, and fixes downloaded-media URLs and chat-list name fallbacks.
✨ New Features
Chatwoot Evolution-API Parity
- Auto-provisions (or reuses) an API-channel inbox on startup and resolves its id automatically (
CHATWOOT_AUTO_CREATE,CHATWOOT_INBOX_NAME,CHATWOOT_WEBHOOK_URL) - Forwards message edits, revokes, and deletes to Chatwoot as threaded notes, with bidirectional WhatsApp↔Chatwoot markdown and reply/reaction threading via the
WAID:source-id convention - Reopens/reuses a conversation for returning contacts across both the REST and direct-DB import paths (
CHATWOOT_REOPEN_CONVERSATION,CHATWOOT_CONVERSATION_PENDING), plus aCHATWOOT_IGNORE_JIDSignore list - Routes agent replies back through the resolved device (no cross-account mis-delivery in multi-device deployments) and replays transient WhatsApp→Chatwoot failures from a durable retry queue
- Adds agent signature support (
CHATWOOT_SIGN_MSG/CHATWOOT_SIGN_DELIMITER), degrades to text when an inbound media download fails instead of dropping the message, and expands unit coverage (chatwoot 22%→58%, pgimport 28%→74%)
Saved WhatsApp Contact Name in Chatwoot (#714)
- Resolves the saved address-book name (
FullName→PushName→BusinessName) from the local WhatsApp contact store for 1:1 chats, instead of surfacing the bare phone number - Prefers it over the event pushname for both incoming and outgoing 1:1 chats, falling back to pushname then the phone identifier (fixes #688)
Session ID in Webhooks & JID in App Responses (#717)
- Webhook payloads now include
session_id, resolved from the device JID at the single forward chokepoint, so multi-tenant events can be correlated back to the session registered viaPOST /devices(closes #578) GET /app/statusandGET /app/devicesnow return the WhatsAppjidalongsidedevice_iddevice_idstays the JID for backward compatibility;session_idis omitted when the JID isn't mapped- Documented in
openapi.yamland the webhook payload docs
🐛 Bug Fixes
Honest WhatsApp Error 463 Handling (#708)
- Removes the client-side pre-warm + token-wait + one-shot retry around error 463 (
NackCallerReachoutTimelocked), which re-sent without a usable token and simply 463'd again, adding latency and a second reach-out attempt - Error 463 is WhatsApp's server-side reach-out timelock: the trusted-contact token can only arrive from the recipient (inbound message, privacy-token notification, or history sync), so whatsmeow's built-in tctoken lifecycle is the only correct path
- Keeps the 463→429 mapping and rewrites
WA_REACHOUT_TIMELOCKguidance to describe the restriction and its real remedies
Public URL for Downloaded Media (#716)
- Media download responses now return the public URL for the saved file
- Aligns the media-download device context in the REST path
Chat-List Name Fallback (#715)
ListChatsandGetChatMessagesnow fall back to a JID-derived label when the stored chat name is empty (phone number for 1:1,Group <id>/Newsletter <id>, andStatusforstatus@broadcast)- Fixes blank names for chats persisted before a pushname/group subject was known (fixes #675)
Chatwoot pgimport UUID Cast (#724)
- Drops the
::textcast ongen_random_uuid()for the uuid-typedconversations.uuidcolumn - Fixes the INSERT failing with
column "uuid" is of type uuid but expression is of type texton strict/default Chatwoot schemas, which had dropped every chat whose contact was newly created
🔧 Technical Improvements
Documentation & Tooling
- Refreshed the
AGENTS.mdhierarchy for the current branch, runtime paths, Chatwoot conventions, and release workflow - Added an
update-release-noteagent skill
Dependency Updates
go.mau.fi/whatsmeow: refreshed to latest across several updates- Related Go dependency checksums refreshed through
go.mod/go.sum
What's Changed
- fix(chatwoot): use saved WhatsApp contact name instead of phone number (#688) by @aldinokemal in #714
- fix(chatwoot): trim whitespace from API token and URL (#674) by @aldinokemal in #713
- fix(chat): fall back chat-list name to phone number when empty (#675) by @aldinokemal in #715
- feat(api): add session_id to webhooks and jid to app responses (#578) by @aldinokemal in #717
- fix(media): return public URL for downloaded media by @aldinokemal in #716
- feat: chatwoot integration updates by @aldinokemal
- fix(send): stop retrying WhatsApp error 463 and surface it honestly by @aldinokemal
- fix(chatwoot): drop ::text cast on conversation uuid in pgimport by @aldinokemal
- chore: update whatsmeow to latest by @aldinokemal
Full Changelog: v8.7.0...v8.8.0
v8.7.0
What's New in v8.7.0
🚀 Minor Release: Presence Pulses, Quoted Media Replies, Send Retry Reliability & WhatsApp Edit Compatibility
This release adds scheduled presence pulses, lets media send endpoints quote existing messages, improves recovery from WhatsApp 463 send failures, fixes encrypted message-edit handling for newer WhatsApp clients, and preserves WhatsApp store identity after restart.
✨ New Features
Scheduled Presence Pulse (#692)
- New scheduler periodically marks connected logged-in devices as
available, then returns them tounavailable - Configurable through defaults, environment variables, CLI flags, and both REST/MCP startup paths
- Documents the behavior in the README, webhook payload docs, and example environment file
- Adds focused scheduler coverage for connected-device filtering, timing, and shutdown behavior
Quoted Media Replies (#705)
- Image, file, video, and audio send requests now accept optional
reply_message_id - Media sends reuse the existing reply-context flow, so stored messages can be quoted without changing response shapes
- Embedded media send forms and OpenAPI docs now expose the reply field
- Adds repository/wrapper support plus validation and usecase regression coverage
🐛 Bug Fixes
WhatsApp 463 Send Pre-Warm and Retry (#695)
- Detects WhatsApp reachout timelock/server error 463 and pre-warms 1:1 recipients before one retry
- Runs a WA-Web-style presence, subscribe, composing, and paused sequence bounded to 3 seconds
- Skips groups, broadcasts, newsletters, and non-463 errors so existing send paths remain unchanged
- Improves
WA_REACHOUT_TIMELOCKguidance when Meta still rejects the send
SecretEncryptedMessage Edit Compatibility (#693)
- Decrypts
SecretEncryptedMessage{MESSAGE_EDIT}envelopes sent by newer/LID-migrated WhatsApp clients - Materializes encrypted edits into the legacy
ProtocolMessage{MESSAGE_EDIT}shape for existing chat storage, webhook, and auto-reply paths - Fixes edited-message webhooks so
message.editedincludes the original message ID and edited body again - Keeps legacy-format edits unchanged and falls back to previous behavior when decryption is not possible
Preserve WhatsApp Store Identity on Restart (#706)
- Existing devices keep their persisted whatsmeow JID when loaded after restart
- Non-AD device IDs can reuse persisted AD store devices
- Optional keys DB sync now copies only the current device instead of deleting unrelated key parents
- Fixes device registration paths that could come back with an empty instance JID after restart
🔧 Technical Improvements
Runtime and Repository Hygiene
- Reorganized
.gitignoreinto logical sections and addedstatics/for runtime media, QR codes, and send items (#690) - Replaced the legacy
CLAUDE.mdguidance with scopedAGENTS.mdinstructions for the repository and key modules
Dependency Updates
go.mau.fi/whatsmeow: updated to latestgo.mau.fi/libsignal: updated to v0.2.2- Related Go dependency checksums refreshed through
go.modandgo.sum
What's Changed
- feat: add scheduled presence pulse by @aldinokemal in #692
- fix: pre-warm and one-shot retry on WhatsApp send error 463 by @aldinokemal in #695
- fix(webhook): decrypt SecretEncryptedMessage for message edits by @nasirnaqash in #693
- fix(webhook): add support for SecretEncryptedMessage edits by @aldinokemal
- chore: reorganize .gitignore and add statics/ by @fadlank-noer in #690
- feat(send): support quoted media replies by @aldinokemal in #705
- fix: preserve WhatsApp store identity on restart by @aldinokemal in #706
- chore: update whatsmeow to latest by @aldinokemal
New Contributors
- @nasirnaqash made their first contribution in #693
- @fadlank-noer made their first contribution in #690
Full Changelog: v8.6.0...v8.7.0
v8.6.0
What's New in v8.6.0
🚀 Minor Release: Message Edit History, Message Reactions, Label Webhooks & Pure-Go SQLite Builds
This release persists WhatsApp message edits and reactions into chat history, forwards label appstate events to webhooks, adds pure-Go SQLite builds for easy ARMv6/ARMv7 cross-compilation, and ships several reliability fixes around message sending and chat list scoping.
✨ New Features
Persist WhatsApp Message Edit History (#679)
- New
message_editstable tracks everyMESSAGE_EDITevent as an append-only history - The original message content is updated in-place while previous revisions remain queryable
- Existing edit webhook payload behavior is preserved
Persist Chat Message Reactions
- New reaction storage with migration and chat/search hydration
- Inbound and history-sync reaction events are persisted separately from messages
- Reactions exposed in chat responses and documented in OpenAPI, with regression coverage
Forward WhatsApp Label Appstate Events to Webhooks (#659)
- Label edits and chat–label association changes are now forwarded to configured webhooks
- Reuses existing webhook forwarding, event filtering, and LID normalization behavior
- Payloads include label metadata, label state,
chat_id, andchat_lidwhen applicable
Pure-Go SQLite Support via Build Tags (#677)
- New
pkg/sqliteabstraction switches between CGO-basedmattn/go-sqlite3(default) and pure-Gomodernc.org/sqlite(with-tags purego) - Enables easy cross-compilation for Raspberry Pi (ARMv6/ARMv7) and other platforms without a C toolchain
- Pure-Go path applies tuned pragmas (
busy_timeout=30s, WAL,synchronous=NORMAL) to preventSQLITE_BUSYon slow MicroSD cards - Default build behavior is unchanged — fully backward compatible
- README includes a Raspberry Pi cross-compilation guide
Phone Number in Contact Messages (#646)
- Outgoing contact messages now store name + phone number together in saved content
- Contact message text extraction includes the phone number from the vCard
- Added unit tests for proto and event contact text formatting
- Closes #645
Gowa Favicon (#672)
- Web client now shows the Gowa favicon in the browser tab using the existing
gowa.svgasset
🐛 Bug Fixes
API-Sent Messages Missing from Chat List (#683, PR #687)
wrapSendMessagepreviously usedcontext.Background()for async storage, which dropped the device instance attached byContextWithDevice- This caused rows to be stored with an empty
device_idwhile queries used the logged-in JID — a device scoping mismatch surfacing astotal_messages=0 - Fix uses
context.WithoutCancel(ctx)so device context survives after the HTTP request completes StoreSentMessageWithContextnow returns an explicit error whendevice_idcannot be resolved (no more silent bad data)deviceChatStorage.StoreSentMessageWithContextinjects device scope when context lacks one (defense in depth)- Note: messages previously stored with empty
device_idwill not appear until re-sent or re-synced via history
Mitigate WhatsApp Server Error 463 on Send
- Privacy token storage is kept on the primary WhatsApp database while the optional keys cache is used only for encryption and session data
- WhatsApp server error 463 is mapped to a
WA_REACHOUT_TIMELOCKresponse so API callers get actionable retry guidance - Documented safer
DB_KEYS_URIusage with regression coverage for the store and send-error behavior
🔧 Technical Improvements
CI / Docker
- Publish the
latestDocker manifest on each release
Dependency Updates
go.mau.fi/whatsmeow: updated to latest- All Go dependencies kept up-to-date
What's Changed
- Add Gowa favicon by @juliomuhlbauer in #672
- Add pure-go sqlite support with build tags for easy armv6/armv7 cross-compilation by @danihidayatx in #677
- Forward WhatsApp label appstate events to webhooks by @zayedadel in #659
- fix: API-sent messages missing from chat message list (#683) by @aldinokemal in #687
- feat: persist whatsapp message edit history by @juliomuhlbauer in #679
- Show phone number when sending a contact in messages by @juliomuhlbauer in #646
- feat: persist chat message reactions by @aldinokemal
- fix: mitigate WhatsApp 463 send failures by @aldinokemal
- ci: publish latest docker manifest on release by @aldinokemal
- chore: upgrade version by @aldinokemal
New Contributors
- @danihidayatx made their first contribution in #677
- @zayedadel made their first contribution in #659
Full Changelog: v8.5.1...v8.6.0
v8.5.1
What's New in v8.5.1
🐛 Patch Release: Link Preview Thumbnail Fixes, ARMv7 Builds & Dependency Updates
This patch normalizes link preview thumbnails for WhatsApp link messages, adds ARMv7 Docker and release binary coverage, and refreshes whatsmeow dependencies.
🐛 Bug Fixes
Link Preview Thumbnails
- Normalize link preview images into bounded JPEG thumbnails
- Send the smaller inline JPEG thumbnail for link messages
- Include media key timestamp when uploading preview thumbnails
- Added regression coverage for thumbnail normalization
🔧 Technical Improvements
ARMv7 Release and Docker Builds
- Add ARMv7 Docker image build support alongside ARM64
- Add Linux ARMv7 release binary generation through GoReleaser
- Keep release archive names distinct for ARM variants
Dependency Updates
go.mau.fi/whatsmeow: updated to latest- Related Go dependencies refreshed through
go.modandgo.sum
What's Changed
- fix: normalize link preview thumbnails by @aldinokemal
- ci: add ARMv7 Docker and release builds by @aldinokemal
- chore: update whatsmeow to latest by @aldinokemal
- chore: bump version to v8.5.1 by @aldinokemal
Full Changelog: v8.5.0...v8.5.1