github rmyndharis/OpenWA v0.26.0

latest releases: sdk/go/v0.7.0, java-sdk-v0.7.0, php-sdk-v0.7.0...
3 hours ago

Added

  • Report poll votes as a poll.vote webhook and WebSocket event on whatsapp-web.js: the poll, chat, voter, the option texts now selected ([] when the vote is cleared), the poll question when WhatsApp Web still holds the poll, and the time of the change in seconds and in milliseconds (timestampMs, which orders a voter's changes within one second); the JavaScript, Python, Go and Java SDKs list the event name from SDK 0.7.0 (#1922). Thanks @laviniarochaprec.
  • Add Number Checker page in dashboard for single and bulk WhatsApp recipient pre-validation, in every dashboard language (#1865). Thanks @Purushotham-Prajapati-24.
  • Add the opt-in message.receipt event for per-recipient group and status receipts on Baileys, sent to the webhooks and /events subscribers that name it (#1748); the JavaScript, Python, Go and Java SDKs list the event name from SDK 0.7.0. Receipts are bounded per webhook: a quarter of WEBHOOK_DISPATCH_CONCURRENCY deliveries run at once, shared by a session's receipt webhooks, three quarters of WEBHOOK_DISPATCH_MAX_QUEUED events wait for each, and further receipts are dropped and counted in openwa_webhook_delivery_failures_total. Thanks @KGMA74, and @berodcdev for the request.
  • Record plugin install, update, uninstall, enable, disable, configuration and session activation changes in the audit log.
  • Add QUEUE_PREFIX to set the Redis key prefix of the BullMQ queues, so deployments that share a Redis stop taking each other's webhook and ingress jobs; it covers the queues only, not the rate-limit store or the WebSocket fan-out, and a value that is not 1 to 64 letters, digits, dots, underscores or hyphens starting with a letter or digit fails the boot.
  • Add All, Unread, 1:1, Groups and Unknown quick filters to the dashboard chat list. Thanks @rjsebening.
  • Add a New chat panel to the dashboard Chats page that opens a chat with a saved contact or a number WhatsApp confirms, and on Baileys creates a group. Thanks @rjsebening.
  • Add chat and contact menus to the dashboard Chats page: pin, mute, archive, mark unread, save, rename and block a contact, leave a group, and clear or delete a chat on whatsapp-web.js only until #1891 is fixed. Thanks @rjsebening.
  • Add an Archived view to the dashboard chat list; archived chats no longer appear in the main list unless searched, and pinned chats lead it. Thanks @rjsebening.
  • Add a group info dialog to the dashboard Chats page that edits a group and manages its participants. Thanks @rjsebening.

Changed

  • Upgrade Puppeteer to 25.16.0 with Zod 4, preserving whatsapp-web.js browser teardown.
  • Update fast-uri to 3.1.9 and ip-address to 10.7.4.
  • Update dashboard validation tooling to Zod 4.6.7.
  • Support Guzzle 8 in the PHP SDK while retaining Guzzle 7 compatibility, from PHP SDK 0.7.0 (#1870).
  • Reject empty PHP SDK defaultHeaders lists before sending requests, from PHP SDK 0.7.0; use '' for an empty header value (#1870).
  • Convert legacy message types in a one-time migration instead of on every boot, which read the whole messages table before the HTTP port opened.
  • Open the HTTP port after at most 60 s of plugin restore; plugins still restoring finish in the background.
  • Apply the hooks of a plugin still restoring only once it is up, and answer 409 to enabling, disabling, updating, uninstalling or reinstalling it until then.
  • Track a plugin's storage use as a running total instead of rescanning every stored key on each write.
  • Request only the profile pictures the dashboard chat list does not hold yet, and keep the current ones on screen while the rest load.
  • Remove the unused STORAGE_LIST_MAX_FILES setting; a leftover value is ignored instead of validated at boot.
  • Remove the INFO permission from the bundled Docker socket proxy; nothing used it, and GET /info through the proxy is now refused.
  • Drop the archiver dependency; the storage export writes its tar.gz with tar-stream and zlib.
  • Remove a stored plugin secret when a config write sends null for it, where null used to keep it; an empty string still keeps it.
  • Send null for a cleared secret field in the plugin config form of the dashboard, which removes the stored secret.
  • Reuse the participant list of a Baileys group for up to 5 minutes (BAILEYS_GROUP_METADATA_CACHE_TTL_MS, 0 queries for every send) instead of querying it for every group message.
  • Close a /events WebSocket connection that sends a client frame over 8 KiB, where Socket.IO's default allowed 1 MB; the subscribe frames of the dashboard and the SDKs are under 1 KB.
  • Cut an inbound poll to 32 options and 1024 characters per option and for the question, far above WhatsApp's own limits, so a malformed poll cannot bloat stored rows, webhooks or socket frames.
  • Refuse a JSON boolean or object in a string field of a request body with 400; they used to be converted to text, so {"text":{"a":1}} sent the customer [object Object]. A JSON number is still converted.
  • Refuse an array where a request body expects an object (bulk options, customLinkPreview, bulk item content and media, infrastructure config sections, status media sources) and an object where it expects a list with 400; they were stored as defaults or as an empty list.
  • Refuse a request body nested more than 100 levels deep with 400; a body a few thousand levels deep used to answer 500 with a stack trace in the error log.
  • Refuse a muteUntil above 9007199254740991 on POST /api/sessions/{sessionId}/chats/mute with 400; larger values are not exact integers, and from 2^63 they wrapped the Baileys int64.
  • Mark the fields the gateway always sends as required in the JavaScript, Python and Go SDK response types and drop the null it never sends; ParticipantsResult no longer extends SuccessResult in the JavaScript and Python SDKs, and the key validation role is typed admin, operator or viewer in the JavaScript and Python SDKs, from SDK 0.7.0.
  • Hold the dashboard automatic mark-as-read while the browser tab is hidden and send it when the tab is visible again.
  • Acknowledge, when the dashboard opens a chat on Baileys, up to the 100 newest loaded incoming messages it has not acknowledged yet, where it acknowledged only the newest.
  • Add builtInSet to the database, redis and storage sections of GET /api/infra/config, true when the saved configuration states the built-in mode; a host pinned by the environment now decides builtIn.
  • List message.receipt among the webhook events of the Webhooks page and say in the description of * that it does not deliver message.receipt.
  • List in envPinned of GET /api/infra/status any of the 31 settings a save of the Infrastructure page writes that the environment pins, where 0.25.0 listed eight, and report the storage endpoint there in S3 mode; the page names each pinned setting beside its field and its restart warning follows the pinned value.
  • Add running to the database, redis and storage sections of GET /api/infra/status, true while a bundled container OpenWA created runs and this instance's built-in flag is set, whether or not the service uses it, and false for one Docker Compose created; it is absent while Docker is unreachable.
  • Accept a database or storage section without type on PUT /api/infra/config, keeping the stored type.
  • Serve every page after the first of GET /api/sessions/{sessionId}/chats and GET /api/sessions/{sessionId}/contacts from the list the engine returned within the last 5 s, so walking a large account costs one engine read instead of one per page.
  • Name the Go SDK status font constants after the WhatsApp protocol (StatusFontSystemText, StatusFontFBScript, StatusFontMorningBreezeRegular, StatusFontCalistogaRegular, StatusFontExo2ExtraBold, StatusFontCourierPrimeBold), from Go SDK 0.7.0; the old names, which did not match the font each id selects, stay as deprecated aliases.
  • Refuse to start with REDIS_TLS=true or a REDIS_USERNAME other than default beside the built-in Redis, which serves plain TCP and only its default user, where every Redis connection failed; the Infrastructure page refuses to save that combination.
  • Log an error at start for STORAGE_TYPE=s3 with an S3_ENDPOINT that is not an absolute http:// or https:// URL, where every S3 call failed and media went to local disk, and refuse a save from the Infrastructure page that changes it or newly selects S3 beside it.

Fixed

  • Update multer to 2.5.0 and undici to 8.12.3 to prevent request teardown and response decoding failures.
  • Patch npm's bundled Undici to 6.29.1 in the production image and JavaScript SDK publishing toolchain.
  • Keep deleted sessions free of late message, status and bulk writes, and of the message:failed event of a bulk item whose session was deleted before it was sent.
  • Retain sending limits while hooks or preparation are pending, including across UTC midnight.
  • Preserve whatsapp-web.js readiness when an earlier QR render completes.
  • Cancel stale Message Tester actions after logout, key changes or leaving the page.
  • Load every page of the chat list in the dashboard; an account with more than 1000 chats saw only the first 1000.
  • Cut a long chat name in the dashboard on the side its own script ends, so a Latin name on an Arabic or Hebrew page no longer loses its first letters.
  • Shorten the id of an unnamed group on the dashboard Top chats chart to its first and last digits, and fit the Number Checker phone placeholder in every language.
  • Keep the dashboard charts left to right on right to left pages, where the Top chats labels were drawn across their bars.
  • Bound Redis cache commands and discard expired operations during reconnect.
  • Read the top chats of GET /api/stats/messages on SQLite through the createdAt index, where it scanned the whole messages table (3 s over 59,000 messages for a one-day window, with every request waiting), and skip the row lookup for group messages in the top chats of GET /api/stats/sessions/{sessionId}.
  • Preserve literal request values in custom ingress acknowledgments.
  • Honor inclusive millisecond lower bounds in built-in search.
  • Stop the dashboard from asking Baileys for live chat history on every chat it opens; Baileys answers 501 to it, which logged a failed request each time.
  • Preserve replacement plugin search providers during worker recovery.
  • Answer 409 instead of a bare 500 when WhatsApp Web reloads its page under a running whatsapp-web.js read, and no longer report a group, contact, label or invite lookup cut short that way as not found (404); accepting an invite answers 503 because the join may have landed.
  • Answer 409 naming the path and the cause on GET /api/infra/storage/files/count and /export when the local storage directory holds a symbolic link, a directory the app cannot read or one nested 20 levels deep, instead of silently leaving those files out of the count and the archive.
  • Normalize CRLF line endings in SDK contract parsers so Windows checkouts do not produce false contract mismatches (#1885). Thanks @Purushotham-Prajapati-24.
  • Exclude local repository metadata from Java SDK Maven Central bundles built with Maven 3.10 (#1879).
  • Bound a silent Redis connection in the rate-limit store with a socket timeout, so only the first request waits out a timeout.
  • Delete a session's messages, webhook outbox events and webhook delivery failures in batches, with a new index on outbox events by session, so removing a session with a large history does not hold the database in one statement.
  • Answer 503 instead of 500 when WhatsApp answers a Baileys message send, reaction, edit, delete, pin or status post with a throttle (429) or timeout (408) code, and free the idempotency key of a throttled send.
  • Match a built-in search chat or sender filter against every stored form of the id (@c.us, @s.whatsapp.net, lid), and a group message's author for the sender.
  • Probe a whatsapp-web.js session that awaits operator action, so the watchdog tells a live page from a dead one.
  • Answer 409 instead of 500 or 503 for a whatsapp-web.js send, status post, profile, group, label, mute or pin request that races a session teardown.
  • Reload the dashboard chat list at most once at a time per session when live events arrive in a burst.
  • Keep storage export entry names identical to the stored keys; a backslash, a doubled slash or a leading name: segment used to rewrite them.
  • Leave S3 folder placeholders (keys ending in /) out of the storage export.
  • Keep the chat media and status media orphan sweeps from overlapping when a pass outlasts its interval.
  • Answer 400 for a media conversion url that is not an absolute http(s) address instead of the blocked-address message; the answer names url outside production or with VALIDATION_ERROR_DETAIL=true.
  • Stop an automation rule edit from undoing an overlapping edit or bringing back a rule deleted meanwhile.
  • Make one direct attempt per further stranded webhook outbox row once its receiver has failed in the same sweep, so an unreachable receiver does not hold the sweep for a full retry schedule per row.
  • Keep a session start that worked when the database fails a read after the engine came up, which made the retry answer "already started".
  • Answer a session delete normally when handing back its claim fails after the delete committed.
  • Clear a logged-out session's phone before the status write, so a failed write cannot leave it in the boot auto-start query.
  • Log a failed wipe of the stored messages and chat states when a Baileys session is unlinked.
  • Answer the number check with the phone id (<phone>@c.us) on whatsapp-web.js for a contact migrated to LID addressing, as Baileys does.
  • Read a list of lid allowlist entries from the database in one query instead of one per unseen entry first.
  • Keep a plugin hook handler that outlives the hook timeout inside the re-entrancy guard, so its late sends no longer re-fire the event and dispatch the handler again.
  • Complete a data import on PostgreSQL when clearing a large messages table outlasts the statement timeout.
  • Create the built-in PostgreSQL container with the saved DATABASE_PASSWORD, so a custom password saved with the built-in database no longer fails the next boot; a data volume that already exists keeps the role password it was created with.
  • Serialize a plugin upload with an uninstall or update of the same plugin id.
  • Show hours for a session active within the last day on the Sessions page, as the Dashboard does.
  • List a whatsapp-web.js contact held under both its LID and its phone once in the contacts and blocked lists, and give it the phone as number in the contacts list and by id, where the first copy carried the LID digits.
  • Clear both entries when unblocking a whatsapp-web.js contact blocked under its phone number and its LID; it stayed in the blocked list after a successful unblock.
  • Show a localized edited indicator in Chats for observed edits before or after storage, including after reload; deleted messages retain their placeholder (#1905). Thanks @yederlv.
  • Store messages delivered already deleted as empty tombstones and clear earlier history and search content; suppress repeated content in hooks and events when a stored tombstone can be read.
  • Answer 409 to a start of a session whose delete is still running; it started an engine that stayed registered for the deleted session.
  • Preserve a pending session delete's lease fields when another stop arrives.
  • Stop an older delete before its next removal transaction after ownership changes; a failed claim write answers 503 without removing data.
  • Keep a session create, a session delete and a plugin instance delete on SQLite out of another request's open transaction, whose rollback could undo a write already answered as done and bring a deleted session back after a 204; each waits up to 10 s for that transaction to end.
  • Stop two session creates or plugin instance deletes at the same moment on SQLite from leaving every later data import refused with 409 until a restart.
  • Save a webhook or automation rule without a transaction, so on SQLite one that fails because its session was just deleted no longer undoes the writes other requests made meanwhile.
  • Preserve pending start retries when a session delete is refused before removal, while retaining independent operator stops.
  • Prevent in-flight session starts and expired-owner renewals from undoing an accepted stop.
  • Prevent overlapping session reconnects from leaking engines or retiring their replacements.
  • Commit successful expired-owner batch cleanup with the ownership transition, so delayed cleanup cannot fail a later owner's new batches.
  • Remove the auth directories a deleted session's name keyed before 0.23.5 on PostgreSQL too; a failed id lookup left the old WhatsApp login on the volume.
  • List a Baileys contact blocked under both its LID and its phone once in the blocked list, as whatsapp-web.js does.
  • Hold a queued ingress delivery and a reconciler replay for a plugin still restoring after startup without spending a retry attempt, instead of dead-lettering it; held deliveries wake together, which keeps their order apart from the limits docs/25 states.
  • Answer 400 for a GET /api/search dateFrom or dateTo outside the safe integer range and publish that range in the schema; on PostgreSQL a value past about 9.2e21 answered 500.
  • Answer an empty page instead of 500 for a list offset of 2^63 or more on the audit, session, webhook, delivery failure and message lists.
  • Stop a Redis that stops answering or answers slowly from making the WebSocket publish client hold every event in memory: a connection silent for 10 s is dropped, events raised while it is down and during its reconnect backoff of up to 5 s are dropped for clients on other replicas instead of replayed late, and a slow Redis is held to about 12,000 unanswered events plus roughly 2 s of traffic.
  • Ping the Redis subscriber connection of the multi-replica WebSocket fan-out every 15 s and replace it after 30 s without an answer, so a connection a proxy holds open after Redis stopped answering costs other replicas' events for about 45 s at most instead of until it closes.
  • Count in GET /api/infra/storage/files/count only the keys the export carries, leaving out S3 folder placeholders and unsafe keys.
  • Fall back to the 5000 ms default for a negative SIMULATE_TYPING_MAX_MS, which cut the typing pause to about 1 ms.
  • Never fail a log call on metadata that holds a circular reference, a BigInt or a throwing getter, and redact secret-named keys at any depth instead of the first five levels, including on an object with its own toJSON.
  • Bound the copy of log metadata: below the fifth level at most 1000 more objects and arrays are written and an array or object that does not fit what is left of that budget is written as [Truncated]; an array, Map or Set of more than 1,000,000 entries is [Truncated] at any level.
  • Bound one copy of log metadata to 2,000,000 entries (object keys and array lengths, three to a Map's pair) and 2^26 characters (string values, key names and BigInt text, a digit counted sixteen times), a shared value counted at every use, and write what does not fit as [Truncated]; a few references to one large value expanded into a copy or a line of unbounded size.
  • Write an Error in log metadata as its name, message and stack, a Date as its ISO text, a boxed string, number or boolean as its value, a Map or Set as an array of its entries and a Buffer or typed array as its type and length; they were written as {} or one field per byte or character.
  • Redact credentials at any depth in the Baileys wire log (BAILEYS_LOG_LEVEL) and copy nested errors before they are written.
  • Leave functions out of log lines; a function with its own toJSON used to be written.
  • Write a plugin log call whose meta is a string, array, boxed string, typed array, Map, Date, Error or class instance as one meta field instead of one field per character or index.
  • Measure the log metadata of a sandboxed plugin as the logger writes it, a credential value as [REDACTED] included, and replace one that exceeds 8192 values and characters by the size marker without building its text first, so it can no longer pass the cap and a large or heavily shared object no longer blocks the host.
  • Relay a non-string log message of a sandboxed plugin as [non-string log message].
  • Stop ctx.logger.error called without an error from writing the trace undefined.
  • Stop an in-process plugin's ctx.logger call from throwing on a message, error or meta it cannot read, such as a Symbol, a revoked Proxy or a throwing toString; the line is written with [Unprintable] or [Unreadable] in its place.
  • Apply a delete for everyone, an edit or a reaction that reaches a whatsapp-web.js message on SQLite while its contact or media is still being fetched, instead of hooking, storing and announcing the message with its deleted or unedited content.
  • Store a Baileys reaction to a message that is still downloading its media with the message on SQLite, as on PostgreSQL, and announce it and an edit of the same window once the gateway has the message.
  • Keep Baileys chat pin, mute and archive state from reverting, or coming back for a deleted or unlinked chat, or reading as defaults for a lid-addressed contact, when a restore, a session start or a chat-list read overlaps a change; a change written under a contact's lid while its state is merged onto the phone number, or in the same millisecond as the phone entry, is no longer lost.
  • Refuse a chat id that is not in the form localpart@host in the MCP tools SessionSubscribePresence, SessionMarkChatRead, SessionMarkChatUnread and SessionSendChatState, as their REST routes do.
  • Send the method of a raw client.Do (Go) or client.request (JavaScript) call in upper case, so a lowercase verb no longer reaches the gateway as patch and answers 400, from SDK 0.7.0.
  • Refuse every spelling of / (//, /., /.., a link to /, <link to />/..) for OPENWA_DATA_DIR, the state directories, MAIN_DATABASE_NAME, DATABASE_NAME and BOOTSTRAP_KEY_FILE in scripts/restore.sh, and resolve .. after a symlink as the kernel does; run as root, its safety snapshot could copy the filesystem into itself.
  • Describe the 400 of POST /api/sessions/{id}/messages/react and /delete as Session not active or invalid request in the OpenAPI schema; a missing message answers 404.
  • Keep the Infrastructure page from reverting a database, Redis, queue or storage switch that was saved and awaits a restart, and from deleting the stored credentials of the backend it switched away from, on the next save.
  • Seed the Use Built-in toggles of the Infrastructure page from the saved configuration, add a pending-restart note per card and warn in the restart dialog for a switch between a bundled and an external service or to another S3 bucket or endpoint; settings typed beside a switch awaiting a restart were discarded on save.
  • Stop a save of the Infrastructure page from writing an untouched database type, storage type or Redis switch that the environment pins over the stored choice.
  • Treat a host that reaches a bundled container by its container name, another letter case or a trailing dot as that container in GET /api/infra/status and on the Infrastructure page, whose Use Built-in toggles, notes and switch warnings follow it.
  • Preserve saved bundled database and S3 connection details beneath host pins, including PostgreSQL TLS when the built-in choice is omitted.
  • Disable PostgreSQL TLS when an Infrastructure save selects the bundled database for the next boot.
  • Keep saved PostgreSQL and S3 credentials with their selected service host; inactive SQLite and local-storage settings no longer seed a newly selected service.
  • Clear supported legacy S3 credential aliases when switching to local storage.
  • Start a built-in container at boot only for a datastore in use whose host the environment does not pin elsewhere; a restart from the Infrastructure page, whose dialog names each container it stops, stops the built-in Redis when Redis is switched off and neither the queue nor the cache uses it, and a container the dashboard created whose host the environment now pins to another server. A leftover flag started a container nothing used.
  • Allow saving the Infrastructure page with the PostgreSQL pool size field emptied, which sent null and answered 400.
  • Allow saving a plugin config whose secret field declares a pattern, which the stored mask failed.
  • Stop the Webhooks and Templates pages from sending operator-only reads with a viewer key.
  • Stop the Sessions and Plugins pages from showing "No sessions found" or "No plugins installed" under the error banner when the first list read failed.
  • Keep the dashboard chat thread where the reader is: reopen a chat left at the bottom at its newest message, keep a saved position while images decode, scroll to new messages the account sent elsewhere, and key the saved position by session and chat.
  • Keep a dashboard chat reader on their message when several images above it decode at once, when images decode above it while older messages load, and when they open another chat and come back while those messages load or after they arrived.
  • Scroll a message sent while its chat or an older page is still loading into view, keep a reader at the bottom of the chat when an image decodes as their sent message is confirmed, and open a chat at its own saved position when an image of the previous chat was still decoding.
  • Keep keyboard focus on the Copy button after a copy where the Clipboard API is missing (plain HTTP), on the API keys, webhooks, templates, plugins and Infrastructure pages.
  • Fix the dashboard layout on phones, short and half-screen windows: the Dashboard sessions table, Sessions cards, login screen, API key names, install dialog header, webhook filter rows and popover, Infrastructure, Plugins and Templates columns, page headers, Infrastructure card titles and the Chats search scope in longer languages, the Plugins rail and its config dialog's tab strip, the Templates session select, the closed navigation drawer's edge, the language menu, icons at larger text sizes and the page height under a phone browser toolbar.
  • Improve dashboard keyboard focus, text contrast and right-to-left layouts, and honor reduced motion.
  • Stop the Message Tester multi-group send when the session is not running or the gateway cannot be reached, instead of recording one failure per remaining group.
  • Read phone numbers typed in native digits in the Message Tester, bulk list, webhook filter, pairing field, the chat, channel and status searches, the status recipient search and the chat access list of an API key.
  • Keep the database, Redis and S3 secret fields of the Infrastructure page from being filled by a browser's password manager with the saved dashboard key.
  • Keep an error toast above an open image in Chats; it was painted under the lightbox backdrop.
  • Style the Instances tab of the Plugins page: its action buttons, the delete dialog and rows that wrap.
  • Colour the status pill of the session details dialog for every session status.
  • Fix the phone navigation drawer: it no longer shows icons only after the sidebar was collapsed on a wider window or covers its close button, it closes with Escape and leaves the tab order while closed, and its menu button announces Close and its expanded state.
  • Keep icons from shrinking in flex rows at a larger browser text size.
  • Release the database connection of a data import on PostgreSQL when its transaction cannot begin.
  • Answer 400 instead of 500 for a template or bulk variable that has no text form.
  • Report as message.ack a delivery or read of a one-to-one message the account sent, or a server rejection, that Baileys folds into the message's own upsert when the two land within about 100 ms of each other; it was dropped and left the message at sent.
  • Send the stored author as the sender of a Baileys group read receipt for a message the engine no longer holds, and leave out an id with no known author; such a receipt carried no sender and still answered success: true.
  • Read a Baileys inbound poll sent under the pollCreationMessageV4 or pollCreationMessageV5 content key as a poll with its question and options instead of an empty unknown message.
  • Stop whatsapp-web.js from announcing message.edited and overwriting the stored caption when WhatsApp Web only replaces a media thumbnail or a location preview.
  • Retry a whatsapp-web.js profile picture asked for by phone id under the contact's LID when the phone form answers nothing or fails, for a contact WhatsApp keys by LID.
  • Stop a session that cannot see a contact's number from erasing the number another session stored for that contact's lid, so a key restricted to that number keeps the chat; the number is cleared by the session that stored it, or by any session once that one is deleted, logged out or unlinked.
  • Retry failed LID mapping writes on fresh observations without replaying cached answers as new reports (#1058).
  • Keep LID lookup expiry independent of system clock changes.
  • Refresh cached missing LID numbers on the first message after five minutes.
  • Use shared reported LID mappings consistently for single-chat and chat-list access checks, and withhold stale local aliases after a failed identity change.
  • Preserve LIDs as message and listing IDs, with display phone numbers separate.
  • Require an engine-reported LID/phone pair for alias matching in routing, filters and message changes; distinct LIDs remain separate.
  • Wait for the message writes still in flight for the sessions a data import removes, so no message row is left for a removed session; an import that does not commit fails the unfinished bulk batches of those sessions.
  • Name each font in the dashboard status composer (System, System text, Script, Bold, Morning Breeze, Calistoga, Exo 2, Courier Prime) instead of its number, offer only ids 0 to 7 on whatsapp-web.js, which posts 8 to 10 in the default font, and draw a received status with font 1 in the system face instead of a serif one.
  • Throw OpenWATimeoutException from the PHP SDK on a host without ext-curl when the gateway stays silent past timeout, instead of a Guzzle exception, from PHP SDK 0.7.0.
  • Keep a reopened proxy editor open on the Sessions page when an earlier save of the same session answers late.
  • Keep a template draft and a newer delete confirmation on the Templates page when an earlier delete answers late.
  • Keep a new-session draft on the Sessions page when an earlier create answers late.
  • Keep a pasted phone number whole in the pairing field of the Sessions page; a native 15-character cap cut "+62 812-3456-7890" and could request a code for the wrong account. A paste with more than 15 digits, or with text other than digits, a plus and phone separators, is refused with the field error instead of requesting a code.
  • Keep Shift+Tab inside a dashboard dialog when focus is on the dialog itself.
  • Announce a refused sign-in and a failed first load of the Dashboard to screen readers.
  • Count the sessions on the Dashboard from the session list while the statistics are unavailable, where the overview read "Showing 3 of 0 sessions" and the Connected Sessions card read 0.
  • Show a failed session read on the Chats page with a Retry button instead of "No connected sessions".
  • Keep a banner with a Retry button on the Chats page when the chat list cannot be read, instead of "No chats".
  • Fix a slow API key save closing the editor of another key opened meanwhile.
  • Fix a single number check answering after sign-out, a key change or leaving the page.
  • Name a pinned REDIS_TLS or REDIS_USERNAME on the Redis card of the Infrastructure page.
  • List in the restart dialog the bundled containers a disabled datastore leaves behind when its host is pinned to the bundled name.
  • Fix the Number Checker joining a tab-separated index column into the number.
  • Fix the Plugins page showing install state from an older catalog read.
  • Stop a bulk number check at the first 403 instead of failing every number.
  • Fix the restart dialog staying on the waiting screen when the readiness request never answers.
  • Fix an old QR reply replacing a newer code or closing a reopened pairing dialog.
  • Fix the reading position jumping after a chat switch while images were still loading.
  • Fix quoted chat ids in an uploaded recipients CSV being dropped from a bulk send.
  • Retry a hung readiness check during a restart and report a restart request that never answers within five minutes as outcome unknown.
  • Check the plugin instance secret in the dashboard against the 16 to 512 character bounds of the API, counting characters rather than UTF-16 units.
  • Fix a backup restore continuing under a different API key after sign-out.
  • Show the Portuguese dashboard to browsers set to European or generic Portuguese.
  • Remove the hidden field of the clipboard fallback and return focus when copying throws.
  • Keep Tab inside a dialog that has no focusable control, such as the restart progress view.
  • Rebuild the SQLite message search index when it no longer matches messages, so a search stops returning other messages.
  • Bound the PostgreSQL UTC session setup with the connection timeout, so a database that accepts a connection and then stalls fails the connection instead of hanging the boot.
  • Keep chat mute, archive and pin state with its session when the re-key migration meets a session named after another session's id.
  • Fix Baileys chat mute, pin and archive state being lost after a failed database write.
  • Fix a Baileys contact lookup answering with a contact of another identity kind that shares the same digits.
  • Fix Baileys replies carried by contact cards, polls, live locations and orders, and history messages, losing their quoted message and mentions.
  • Fix the Baileys chat list previewing the first of two messages sent in the same second.
  • Fix Baileys call outcomes being lost or misreported when a call ended while its rejection was pending.
  • Stop a Baileys session stopped or deleted while the library loads from creating its auth directory again.
  • Keep phone and LID identities apart when Baileys finds the account's own row in a group, so a member whose LID digits read like the account's number no longer decides isAdmin and isReadOnly.
  • Fix two label changes to one chat made at the same time undoing each other on whatsapp-web.js.
  • Fix a page reload during a liveness probe closing the reload grace window early on whatsapp-web.js.
  • Fix a failed phone-to-LID mapping write on whatsapp-web.js not being retried until restart.
  • Answer 409 instead of 503 for a whatsapp-web.js message or group picture write that races a session stop.
  • Fix GET /api/sessions/{sessionId}/channels/{channelId}/messages answering 404 on whatsapp-web.js for a channel whose id the channel list spells another way.
  • Stop reading a link preview body at its byte cap, so a title past the cap no longer becomes the preview.
  • Stop the batch profile-picture lookup after one times out, so no more than five engine lookups run at once; the ids it skips are left out of the answer.
  • Refuse a boolean startTime on POST /api/sessions/{sessionId}/calls/link instead of reading it as 1.
  • Fix approving or rejecting group join requests acting on every pending request when the body was a JSON list or was sent with a content type other than JSON or form encoding; both now answer 400.
  • Fix saving a contact with a null last name sending the null to WhatsApp Web on whatsapp-web.js.
  • Fix creating a channel with a null description answering a null description on whatsapp-web.js.
  • Fix a group add or create charging a bare number and its @c.us form as two new contacts against the daily cold allowance.
  • Keep the SQLite database file path when the Infrastructure page saves SQLite.
  • Write data/.env.generated through a staged file and a rename, so a failed save keeps the previous configuration; where a rename cannot replace the file, or the directory refuses the staged file, such as on a single-file mount or in a directory the app cannot write, it is written in place, and a symlink is written through to its target.
  • Refuse a configuration save that the next boot would reject, with the boot validation message, instead of persisting it.
  • Reset REDIS_TLS and clear REDIS_USERNAME when the Infrastructure page selects the built-in Redis.
  • Stop a bundled container on restart once its datastore is switched off, even when a pinned host still names it.
  • Prepare the storage export directory before starting the archive and destroy the archive on every failure path.
  • Count the template variables a bulk media field names in the data export media budget.
  • Restore a data backup onto a PostgreSQL schema that lacks an optional table instead of failing with a 500.
  • Start the selected built-in containers once Docker answers after a failed first connection, and report an unreachable Docker inventory as unknown.
  • Bound the Docker image and container inspections with the deadline of the other short calls, and pull an image only when the daemon says it is absent.
  • Wait at most 2 s for the database statistics in a metrics scrape; a late read is kept and served for up to 5 minutes, and the series are omitted only when a read failed or none is recent.
  • Record a session_stopped audit log entry for each session an import with stopOrphans stops, including when the import does not commit.
  • Fix the Python SDK raising ValueError instead of the API error when a response carries an oversized numeric Retry-After, from SDK 0.7.0.
  • Fix the Python SDK API and timeout errors failing to unpickle or copy, so they survive a worker process boundary, from SDK 0.7.0.
  • Ignore a Retry-After beyond the number range in the JavaScript SDK, which read it as Infinity seconds, from SDK 0.7.0.
  • Cap an oversized Retry-After in the Go and PHP SDKs instead of wrapping to a short or negative wait, from SDK 0.7.0.
  • Keep the API error in the Java SDK when a Retry-After date is out of range, where an ArithmeticException replaced it, from SDK 0.7.0.
  • Treat a final 1xx response as an error in the Python, Go and PHP SDKs instead of returning it as a success, from SDK 0.7.0.
  • Keep the query values of a raw path that carries a fragment in the Go, Java and PHP SDKs, where they were sent inside the fragment, from SDK 0.7.0.
  • Send the Idempotency-Key on a Go SDK raw send path that carries a query or a fragment, from Go SDK 0.7.0.
  • Fix the Go SDK treating a 429 whose body could not be read as retryable, and reporting a stalled 429 body as an API error instead of a timeout, from Go SDK 0.7.0.
  • Fix the PHP SDK sending template variables, bulk variables and bulk options as a JSON list when the map has numeric keys or is empty, from PHP SDK 0.7.0.
  • Fix the PHP SDK failing with a TypeError when the gateway answers a typed call with a body that is not JSON; it now throws OpenWAException, from PHP SDK 0.7.0.
  • Read the response of a PHP SDK injected client that sets stream to true inside the timeout handling, so a stalled body is a timeout, from PHP SDK 0.7.0.
  • Stop media conversion from starting new ffmpeg work once shutdown has begun.
  • Fall back to url in POST /api/sessions/{sessionId}/media/convert/* when base64 holds only a data URI prefix.
  • Use the saved proxy for media conversion by URL in a restored session whose id is not a UUID.
  • Drop media supplied through variables from finished bulk batches.
  • Accept a placeholder in content.mentions of send-bulk and validate the rendered WIDs per item.
  • Keep send admissions counted across UTC midnight, including bulk and product sends still waiting on the engine.
  • Hold the typing pause to SIMULATE_TYPING_MAX_MS, jitter included.
  • Drop expired statistics memo entries as new results are stored, so the memo no longer grows with every deleted session.
  • Update only the supplied fields on PUT /api/sessions/{sessionId}/templates/{id} and answer 404 for a template deleted meanwhile, where an overlapping edit was undone or the template came back.
  • Report a status whose row was removed while its media was written as not stored, and delete its file.
  • Accept an empty url next to base64 in status posts and media conversion.
  • Write each dependency file the postinstall scripts rewrite, except the whatsapp-web.js diff patch, to a temporary file and rename it over the original, so a write that fails part-way, such as on a full disk, leaves the file unchanged; a best-effort patch is then skipped, and any other install fails instead of keeping a truncated file.
  • Keep the latest and X.Y image tags from moving to an older release when an older tag is released or re-run.
  • Create the GitHub Release from a notes file, so a CHANGELOG section containing a line that is exactly EOF no longer breaks it.
  • Mirror the PHP SDK to openwa-php only from main and only while its content matches main, so a manual run from another branch or a re-run of an older push no longer replaces a newer mirror.
  • Tell numeric from string enum members in the SDK shape check.
  • Remove the temporary directory of npm run openapi:export after a usage error or a failed bootstrap.
  • Forward S3_ACCESS_KEY and S3_SECRET_KEY in docker-compose.dev.yml, as docker-compose.yml does, so an older .env keeps its storage credentials.
  • Fix deleting a session on PostgreSQL removing the login of another session whose imported id equals the deleted name.
  • Fix a session start that was waiting for its claim relaunching the session after a logout.
  • Fix a stopped or deleted session announcing INITIALIZING after its stop when the first status write replied late.
  • Fix a start that timed out on engine initialization overwriting the status of the engine a restart launched meanwhile.
  • Fix a delete during a reconnect leaving the re-created auth directory of the deleted session on disk.
  • Fix the liveness watchdog recycling an engine that left READY while a probe was in flight.
  • Fix requests for imported sessions with non-UUID ids not being forwarded to the node that runs them.
  • Fix PUT /api/sessions/{sessionId}/presence re-applying its value on the replacement engine when the call outlived the engine it published to.
  • Fix a reconnect that found its session stopped keeping the session counted as running, so its lease was renewed and its MAX_CONCURRENT_SESSIONS slot stayed taken.
  • Fix a message that arrived while its session was being deleted surviving the delete.
  • Stop sending status.received for a status that expired before it was stored.
  • Fix a revoke event without a readable message id erasing an unrelated message that was still arriving.
  • Fix a message reaction not reaching webhooks and the socket when its stored copy could not be written.
  • Cap the receipt deliveries of one session to a quarter of the shared webhook dispatch slots in total, however many of its webhooks name message.receipt.
  • Read an ingress route's conversationId JSON pointer by RFC 6901 tokens: ~1 and ~0 escapes, empty keys and array indexes only.
  • Store the webhook-signature header of a Standard Webhooks ingress delivery as [redacted] in events, queue jobs and dead letters.
  • Stop a WebSocket subscribe overtaken by an unsubscribe from joining its rooms afterwards; it answers SUBSCRIPTION_CANCELLED.
  • Rank a webhook delivery-failure redrive that a plugin cancels behind untried rows, so it no longer heads every batch; the row keeps its payload.
  • Dead-letter a queue job as stall exhaustion only when BullMQ failed it for stalling, not for any handler error that carries the stall text.
  • Retry an ingress delivery whose dispatch error reads bullmq:rateLimitExceeded within its attempt budget instead of re-running it without limit.
  • Bound the wait for the queue producer when ingress dead-letter recovery re-queues a job.
  • End the idempotency key of recurring webhook events (edits, reactions, session and group changes) in a per-dispatch id instead of a timestamp, so two occurrences in one millisecond no longer share a key.
  • Refund the WebSocket handshake attempt that succeeded, not the newest attempt of its address.
  • Fix archived and status media keys embedding Object constructor source text when a sender used the mimetype subtype "constructor".
  • Fix the production default-secret check refusing a built-in PostgreSQL or MinIO addressed by its container name, another letter case or a trailing dot.
  • Keep API key usage counts when a use is recorded while the shutdown flush awaits its write.
  • Fix the storage export skipping its import-limit warnings for files whose size the store does not report.
  • Stop an MCP MessageHistory read when the client disconnects or cancels.
  • Accept base64 media with an empty or non-http url on the MCP media send tools, as the REST routes do.
  • Offload MCP tool results larger than 4 KB of UTF-8, not 4096 UTF-16 units.
  • Accept a fractional timeoutMs in a plugin's net.fetch.
  • Honor an error returned with data from a sandboxed hook handler, as the host chain does.
  • Give hook registrations made in the same millisecond separate ids.
  • Keep plugin storage keys apart: a legacy key spelled like another key's encoded filename no longer reads or deletes that key, and list() returns only keys that get, set and delete accept.
  • Keep a plugin's registry entry until its uninstall has removed its files, so a failed uninstall can be repeated.
  • Carry a plugin's stored data into the new tree before an update swaps it in.
  • Refuse a plugin catalog entry whose id, name, version or text fields are not text, and accept null in its optional fields.
  • Log at start the position and reason of each SSRF_ALLOWED_HOSTS entry that never matches a host: a scheme, credentials, port, path or wildcard, or an IP address not written as the URL parser prints it (127.0.0.1, not 127.1); at most 20 entries are listed, and a value the URL parser rejects is named as such.
  • Run every message:sending handler, a moderation veto included, for a send that a hook handler schedules after its own call has ended; it was skipped as re-entrant.
  • Redrive only the dead letters written under an ingress instance's current session binding.
  • Drop a null optional field from plugin catalog entries instead of answering it.
  • Fix the JavaScript SDK raw requests with a fragment in the path dropping their query parameters.
  • Cap the JavaScript SDK retryAfterSeconds at 2147483 seconds, the longest delay setTimeout accepts once multiplied by 1000.
  • Report the database name to the Infrastructure form only under PostgreSQL, and drop a stored SQLite file path when a PostgreSQL save sends no name.
  • The message list no longer answers an empty page when its after row is deleted while the page is being read.
  • The message list reads its page rows by primary key on SQLite, which cuts a 100 row read of a 300k row session from about 70 ms to 5 ms.
  • Fix the PHP SDK reading a Retry-After header of 309 or more digits as 0 seconds; it is now capped like a smaller oversized value.
  • Do not mark a patch release on an older line as the GitHub Latest release, which the update check reads.
  • Answer a body over BODY_SIZE_LIMIT, malformed JSON, a body nested past the bound and an undecodable path parameter under /api and /mcp with 413 or 400 when the bundled dashboard is served, where they answered 404 Cannot POST (#1921). Thanks @ScalingMBA for the report.
  • Answer a chatId that is not localpart@host on a send or message-action body, and on the MCP message tools, with 400 instead of 500; a bare phone number reached the engine, and every such request counted against the session's send breaker.

Dependencies

  • body-parser 2.3.0 to 2.4.0 for safe compressed-request error handling in the HTTP server.
  • i18next 26.4.2 to 26.4.4 in the dashboard tree, closing a regular-expression denial of service in its nested-translation parser. It ships in the dashboard bundle.
  • The image's Node.js 22.23.3 to 22.23.4, a security release for the http, dns, tls, permission and crypto modules.
  • The image's npm CLI 12.1.0 to 12.2.1, clearing CVE-2026-102276, CVE-2026-102278 (brace-expansion) and CVE-2026-19534 (undici) in its own bundled tree; npm only runs for the documented migration commands, and the three Trivy exceptions for them are gone.
  • The amd64 image's Chrome for Testing 153.0.8010.36 to 156.0.8078.47, picking up the browser security fixes released since; the arm64 image keeps the chromium package Debian ships.

Upgrade notes (behavior changes)

  • Source installs using Puppeteer's downloaded browser move from Chrome 146 to 156; back up SESSION_DATA_PATH (default data/sessions/) before starting and restore it before downgrading Chrome. An explicit PUPPETEER_EXECUTABLE_PATH keeps selecting that browser.
  • ⚠️ Breaking (config). A production boot refuses a METRICS_TOKEN that is the .env.example placeholder or a known weak value such as test or change-me, since the scrape endpoint is public; set a long random token, or leave it unset to keep the endpoint disabled.
  • The HTTP port now opens after at most 60 s of plugin restore. Until a plugin still restoring is up, the hooks it owns (a send moderation veto, for one) do not run, an enable, disable, update, uninstall or reinstall of it answers 409, an ingress route with a response contract answers 503 with Retry-After, and other ingress deliveries are held and dispatched once it is up (with the queue off, by the reconciler after INGRESS_RECONCILE_GRACE_MS); do not rely on a plugin veto for requests in the first minute after a boot with slow plugins.
  • An explicit null for a secret field of a plugin config removes the stored secret, where 0.25.0 kept it; a client that sends null for "not provided" must leave the key out.
  • A client that connects to the Socket.IO root namespace / is refused; connect to /events.
  • Baileys group sends reuse a cached participant list for up to 5 minutes; set BAILEYS_GROUP_METADATA_CACHE_TTL_MS=0 to query for every send as before.
  • GET /info through the bundled Docker socket proxy is refused; a client of your own that reads it needs INFO: 1 on the proxy.
  • Changing QUEUE_PREFIX on a live deployment leaves the jobs queued under the old prefix unprocessed.
  • The first boot on PostgreSQL converts the legacy message types in one statement inside the boot transaction, about 1 minute per million legacy rows while the port stays closed; raise startupProbe.failureThreshold or run the migrations first (docs/14, section 14.5).
  • Session deletion commits bounded batches before the final transaction. Earlier removals survive a later failure; repeat DELETE to finish, or explicitly start the remaining session. Size client and proxy timeouts for large histories (docs/14, section 14.5).
  • During rolling upgrades, 0.25.0 nodes can explicitly start or take over sessions marked deleting or deleting-stopped without clearing those records. Upgrade every node before relying on delete holds, removal fences or serialized batch cleanup.
  • LID mappings inherited from older versions or backups without report-origin data no longer authorize phone aliases until an engine reports the pair. Upgrade every reader together; Baileys now returns LIDs with display phone numbers separate.
  • On SQLite, POST /api/sessions, DELETE /api/sessions/{sessionId} and DELETE /api/integration/plugins/{pluginId}/instances/{instanceId} wait for another open database transaction and answer 503 when it stays open for 10 s: the create stores nothing and the instance delete deletes nothing; a session delete may already have committed earlier batches. Retry after the transaction ends.
  • A storage export or file count now answers 409 on a symbolic link inside the local storage directory (replace it with the real directory or a bind mount), on a directory the app cannot read and on one nested 20 levels deep.
  • Request validation is stricter: a JSON boolean or object in a string field, an object for a list or an array for an object, a body nested over 100 levels and a muteUntil above 9007199254740991 answer 400 where they were accepted or answered 500; a client that posts a number for a string field is unaffected.
  • ⚠️ Breaking (SDK types only, no gateway change). From SDK 0.7.0, the JavaScript, Python and Go SDKs type the response fields the gateway always sends as required and no longer allow the null it never sends, the Go response fields marked required no longer leave a zero value out when marshaled, ParticipantsResult no longer extends SuccessResult in the JavaScript and Python SDKs, and the key validation role is typed admin, operator or viewer in both; code that builds these objects by hand or tests those fields for null needs updating.
  • From SDK 0.7.0, the PHP SDK throws from the constructor for an empty list as a defaultHeaders value, which 0.6.0 accepted; use '' for an empty header value.
  • restore.sh stops before it copies anything, naming the key, when a path from the archive's own .env.generated leads out of the data directory, passes through a link that does not resolve, or lies inside or around another directory the restore refills; set that key in the environment or ./.env to restore there. The default paths and a leftover ./uploads restore as before.
  • The amd64 image moves from Chrome for Testing 153 to 156, so an amd64 rollback to 0.25.0 or earlier crosses a browser major and deletes the stored whatsapp-web.js logins unless sessions/ is restored from a backup taken before the first start on the new image (see docs/11-operational-runbooks.md); Baileys sessions are unaffected.
  • A POSTGRES_BUILTIN, REDIS_BUILTIN or MINIO_BUILTIN flag starts its container at boot only while the datastore is in use (PostgreSQL selected; Redis, the queue or the cache on; S3 storage) and its host is not pinned to another server; a container nothing used is no longer started, and a restart from the Infrastructure page stops one the dashboard created whose host is pinned elsewhere.
  • envPinned of GET /api/infra/status can list any of the 31 settings a save of the Infrastructure page writes, where 0.25.0 listed eight; a client that reads it must expect the database, Redis and S3 keys and the built-in flags too.
  • The dashboard status composer offers only fonts 0 to 7 on whatsapp-web.js, which posts 8 to 10 in the default font; the API still accepts every font id.
  • A boot with REDIS_TLS=true or a named REDIS_USERNAME beside the built-in Redis (REDIS_BUILTIN=true with the bundled host) is refused; set REDIS_TLS=false and clear the username, or use an external Redis.
  • A boot with STORAGE_TYPE=s3 and an S3_ENDPOINT without http:// or https:// (for example minio:9000) still starts, on the local media directory, and logs an error; add the scheme.
  • POST /api/sessions/{sessionId}/groups/{groupId}/membership-requests/approve and /reject answer 400 for a JSON list body or a content type other than JSON or form encoding; they used to act on every pending request. Send {} or no body for that.
  • On whatsapp-web.js, POST /api/sessions/{sessionId}/pairing-code answers 409 for a number that is the stored phone of another session of the gateway.
  • On SQLite the first boot checks the message search index against all stored text and rebuilds it when they differ, so a large database starts more slowly while the port is closed.
  • The idempotency key of recurring webhook events ends in a random id minted per dispatch instead of an ISO timestamp; a receiver that reads its last segment as a time must dedupe on the whole key.
  • An ingress route's conversationId JSON pointer is read by RFC 6901 tokens, so a trailing or doubled slash names an empty key; a delivery such a pointer no longer resolves takes the per-instance lane.
  • A boot with DATABASE_SSL=true beside the built-in PostgreSQL (POSTGRES_BUILTIN=true with the bundled host) is refused; set DATABASE_SSL=false, or use an external PostgreSQL.
  • GET /api/sessions/{sessionId}/contacts/profile-pictures leaves out an id it skipped after another lookup timed out, where an id without a picture or with a failed lookup stays null; a client that indexes the answer by every requested id must handle a missing key.
  • A plugin catalog with an entry whose id, name, version or text field is not text is refused with 400, naming the entry; the default catalog is unaffected, and a private PLUGIN_CATALOG_URL catalog must correct the entry.
  • POST /api/integration/instances/{pluginId}/{instanceId}/redrive replays and counts only the dead letters written under the instance's current session binding; rows of an earlier binding stay in the dead-letter table.
  • A per-session plugin config or an integration instance that sets a net.allowConfigHosts key (for example baseUrl) to another origin than the layer beneath no longer inherits that layer's secret fields, and a list holding one is not inherited either; put the secret in that config or instance, or the plugin reaches the new host without it. A deployment that shared one token across per-session hosts must copy the token to each. A host key that holds a list, an object or a number counts as another origin, and a write to an override or an instance that moves a host key to another origin no longer gets the stored secret back for *** or an empty string; send the secret with it. An instance row loses its own secrets while a per-session override has moved a host key the row leaves unset away from the base config, and a config PATCH of an instance whose plugin is not loaded answers 409.
  • A send or message-action body whose chatId, fromChatId or toChatId has no domain (6281234567890 instead of 6281234567890@c.us), or is a JSON number, answers 400; it answered 500 before and never sent.

Security

  • scripts/restore.sh refuses, before it copies anything and naming the key, a path from the archive's own .env.generated that leads out of the data directory, passes through a link that does not resolve, or lies inside or around another directory the restore refills; an archive someone else built or altered could make it replace directories and files outside the data directory. A key set in the environment or ./.env is honored as before.
  • Template and bulk sends measure the rendered length before building the text: a template send past TEMPLATE_RENDER_MAX_CHARS answers 400, a bulk item whose text or caption would pass it fails, and a bulk media item that would render past the media size limit is refused; a placeholder repeated through the text made the server build the whole text first, which could exhaust its memory.
  • Socket.IO connections to the root namespace / are refused, where an unauthenticated client stayed connected outside the handshake and per-key socket limits; only /events accepts clients.
  • An object sent for allowedChats, allowedSessions or allowedIps when an API key is created or updated answers 400; it was stored as an empty list, which means no restriction, so the key reached every chat, session or address.
  • The secrets of a wildcard integration instance are removed from the plugin config when the instance is deleted, disabled, moved to one session or saved without them; they stayed in use after that.
  • A pairing code request on whatsapp-web.js for a number that is the stored phone of another session answers 409; a key restricted to one session could have WhatsApp revoke the login of another through it. The refusal also tells such a key that a number it tries is held by another session.
  • A session-restricted ADMIN key that creates, moves, disables or deletes an integration instance no longer retires the plugin's all-sessions activation, which silenced the plugin on sessions the key cannot see.
  • A queued or replayed ingress delivery keeps the verified flag recorded when it was accepted, so a plugin update that makes an unsigned route sign no longer labels a payload nobody authenticated as verified.
  • An ingress delivery admitted for one session fails when its instance moved to another session while it waited, where it reached the handler with the other session's config.
  • Disconnect a WebSocket whose API key's stored expiry stops parsing, as REST already refuses it.
  • The message list no longer returns a row that was replaced between page selection and read and no longer matches the requested chat, time window, direction or type; for a chat-restricted key it could be another chat's message.
  • scripts/restore.sh also refuses an archive configuration that puts the admin key file, .env.generated or database.sql on a directory the restore refills, where a link planted in the archive made the copy write outside the data directory.
  • A per-session plugin config or an integration instance that points a net.allowConfigHosts key at another origin than the layer beneath no longer receives that layer's secrets, where a session-restricted admin key could send the operator's stored tokens to a host of its choosing.

Don't miss a new OpenWA release

NewReleases is sending notifications on new releases.