github rmyndharis/OpenWA v0.23.7

6 hours ago

Added

  • Hindi (हिन्दी) dashboard locale, selectable from the language picker. Thanks @probably-ABHINAV.
  • POST /api/auth/validate returns engineType, the engine the gateway runs, so every role can read it.

Fixed

  • A reply, button click or quoting send made from a message:received plugin hook stores the text of the message it quotes, so the dashboard shows that quote instead of an empty box.
  • Baileys: replies to and forwards of an edited message carry the edited text instead of the original.
  • Baileys: a message deleted for everyone answers 404 on reply, quoted send, forward, react, edit, star, pin, unpin and click-button; a reply or forward of it sent the deleted content back to the chat.
  • Baileys: a message deleted for everyone before the gateway finished processing it (while its media downloads, or replayed together with its delete on reconnect) is no longer announced with its content after message.revoked, and does not become the chat preview.
  • Baileys: an edit that arrives while its message is still downloading is no longer lost: message.received, the stored copy and the chat preview carry the edited text.
  • The dashboard dev server proxies only /api/ paths, so a full reload of the API Keys page (/api-keys) loads the dashboard instead of being forwarded to the backend.
  • The dashboard Chats thread renders an @<digits> mention as @FirstName when that participant has posted in the loaded thread and their id matches the digits in the body. This covers whatsapp-web.js, where the author id and the mention carry the same digits. On Baileys the author is normalized to the phone number when the lid mapping is known while the body keeps the lid digits, so those mentions stay as WhatsApp sent them, as does any mention of someone who has not posted. The resolved name renders inside its own <bdi> element, outside Linkify's ignoreTags-respected walk, so a push name can never become a clickable link, however it's spelled. Thanks @TanmayChachra.
  • The dashboard Plugins page shows a plugin's status and type in the selected language; they rendered as raw English values (installed, extension) in every locale.
  • On Baileys, quoting, reacting to, forwarding, editing or deleting a message the moment it arrives or is sent no longer fails intermittently with Message <id> not found: a lookup of a message whose store write is still in flight now waits for that write.
  • On Baileys, a message WhatsApp re-delivers while its first copy is still being stored is no longer dispatched twice.
  • Baileys: an edit, revoke or reaction that WhatsApp delivers inside a wrapper message is no longer dropped, so message.edited, message.revoked and message.reaction fire for it.
  • Baileys: a WhatsApp-side unlink clears the session's stored messages and its persisted chat mute, archive and pin state, and an API logout now clears that state too, so a re-linked account no longer inherits the previous account's flags.
  • Baileys: a failed chat-state read no longer resets a chat's persisted mute, archive and pin flags, and a chat list no longer queries the database once for every chat that has no stored state.
  • Baileys: a chat muted "Always" reads as muted indefinitely instead of unmuted.
  • Baileys: marking read or unread, clearing, archiving and deleting a chat WhatsApp addresses by the contact's lid work with the @c.us id GET /chats returns, instead of answering success: false.
  • Baileys: POST /api/sessions/:sessionId/chats/read without messageIds acknowledges the chat's newest received message and answers success: false when none is known; after a reply sent from the phone it sent no receipt and still answered success: true.
  • Baileys: muting, pinning, labelling, starring and deleting for me a chat WhatsApp addresses by the contact's lid target that chat; mute and pin added a second row for it to GET /chats.
  • Baileys: a chat deleted through the API or on the phone leaves GET /chats, and its stored mute, archive and pin state is cleared.
  • Baileys: a session start reloads its chats' mute, archive and pin state from the database, so a change made on another node shows up.
  • Baileys: a document sent from a URL whose host sends no Content-Type goes out as application/octet-stream instead of application/pdf.
  • Baileys: a call whose rejection failed can be rejected again instead of answering 404, unless the session was disconnected meanwhile.
  • Baileys: two quick archive, mute or pin changes to a chat no longer lose one of the two.
  • Baileys: GET channel and channel subscribe return the channel's name, description, invite code, subscriber count and verified flag, with createdAt as a number; only id and a string createdAt came back.
  • Baileys: delete for everyone on a message the account cannot revoke (another sender's message in a 1:1 chat, or in a group it does not administer) deletes it for the account only, as on whatsapp-web.js, instead of reporting success and deleting nothing. When the group's member list shows no row the gateway can identify as the account, the revoke is still sent.
  • Baileys: the group list and group info recognise the account by its own lid in a lid-addressed group, so isAdmin and isReadOnly are right there, and an admin's delete for everyone in such a group is sent as a revoke.
  • Baileys: the chat preview follows an edit or a delete of the chat's last message, including one made through the API and an inbound revoke.
  • An image, video or audio sent by URL whose host answers with no Content-Type, or a generic application/octet-stream one, goes out as image/jpeg, video/mp4 or audio/mpeg instead of under that generic type, which whatsapp-web.js delivered as a document.
  • Baileys: a message sent through the API becomes the chat's last message, so chat lists sort and preview by it.
  • Baileys: DELETE /api/sessions/:sessionId/status/:id addresses the revoke to the recipients the status was posted to; it was addressed to nobody before.
  • Baileys: BAILEYS_LOG_LEVEL diagnostics keep an error's message and stack instead of logging err: {}.
  • Baileys: a catalog product listed without a price omits price and priceFormatted, and one without a currency omits currency, instead of returning null or a NaN price, and a product card for it no longer sends NaN.
  • whatsapp-web.js: stopping, deleting or logging out a session while Chromium is still launching no longer leaves a logged-in browser running that nothing owns.
  • whatsapp-web.js: a session that reported connected keeps its credentials when the readiness deadline fires right after the event-bridge reload, instead of being forced to pair again.
  • whatsapp-web.js: a chat history read with a zero, negative or non-numeric limit returns the default 50 messages instead of every loaded message, and a plugin's ctx.engine.getChatHistory coerces a non-numeric limit the same way.
  • whatsapp-web.js: button, list and template-button replies arrive as type text, as on Baileys, instead of unknown, and rows stored with the old tokens are backfilled at boot. Rows earlier releases stored as unknown keep that type.
  • whatsapp-web.js: posting a status no longer downloads its own media a second time from the echo event.
  • whatsapp-web.js: the auto-resolved WhatsApp Web pin is refreshed daily instead of held for the life of the process, where it eventually pointed at a build the registry had deleted.
  • A WWEBJS_AUTH_TIMEOUT_MS above about 24.8 days no longer fails every session start with 504.
  • The lid-to-phone cache keeps the most recently written mappings after a restart instead of evicting them first.
  • GET /api/infra/engines lists channels, status updates and catalog among the Baileys features.
  • A session named after another session's id no longer moves that session's WhatsApp credentials onto itself at boot.
  • A session with a reconnect base delay above about two minutes now reconnects while the liveness watchdog keeps reporting its wedged engine, instead of having the reconnect pushed back on every report.
  • A session that recovers on its own after the liveness watchdog scheduled a reconnect is no longer torn down when that reconnect fires.
  • A null maxReconnectAttempts or reconnectBaseDelay in a POST /api/sessions config means the default (unlimited attempts, 5000 ms) instead of turning auto-reconnect off.
  • Message history keeps a revoke or edit that arrives while the message:received hook chain is still running, instead of storing the original content.
  • API_MASTER_KEY is trimmed before the first admin key is seeded, so a trailing newline no longer seeds a key that can never authenticate.
  • On a multi-node deployment, GET /api/sessions and the MCP session tools report engineLoaded: true for a session another live node runs, so the dashboard offers Stop, Logout and Force-kill for it instead of Start. With NODE_URL set on every node those actions are forwarded to the owner; without it only the owner can act on the session.
  • With RESOLVE_LID_TO_PHONE=true, one transient lookup failure no longer stops a sender's phone from ever being resolved again.
  • Bulk media sent as base64 without a mimetype is stored with the mimetype it was sent with, so the media endpoint serves it, and a bulk media URL without a mimetype takes the fetched Content-Type instead of a hardcoded one.
  • Bulk sends pass the recipient chatId in the message:sending and message:failed hook input, as single sends do.
  • Deleting a session during a large history sync no longer leaves orphan message rows, and deleting a session removes its stored statuses and chat mute, archive and pin state at once.
  • GET /api/sessions/:sessionId/presence/:chatId answers JSON null when nothing is reported, as documented, instead of an empty body.
  • Forwarding to a node whose NODE_URL has a path prefix keeps the prefix.
  • Product sends count toward the send failure breaker like every other send.
  • GET /api/plugins/:id/health reports a sandboxed plugin whose worker crashed, failed to enable or is disabled as unhealthy.
  • Removing a row from a plugin config array of masked secrets keeps each remaining secret on its own row. Where the rows cannot be told apart without their secrets (bare secrets, or rows that differ only by a secret), a removal is refused with a request to re-enter the remaining values, instead of keeping the removed secret and dropping a kept one. Deleting one row and adding one in the same save keeps the list's length and is still read as an in-place edit.
  • With QUEUE_ENABLED=true, ingress deliveries that share a provider delivery id across instances or plugins are no longer dropped as duplicates.
  • The ingress reconciler no longer stalls on pending rows of a disabled or deleted instance, and an ingress job failed by BullMQ stall exhaustion writes a dead-letter row and fires ingress:error instead of being lost.
  • The ingress reconciler no longer counts a stranded delivery as delivered when its queued job had already failed; the dead-letter row stays redrivable.
  • An integration instance created without a verifyToken gets a generated one, as documented, so a GET verification handshake can succeed; the dashboard shows it once when the instance is created.
  • ctx.storage.list() no longer returns the plugin package's manifest and package files as storage keys, and an in-place plugin update keeps legacy (pre-encoding) storage files.
  • GET /api/plugins/catalog answers 400 instead of 500 for a catalog with a non-object entry.
  • A plugin instance PATCH whose config is rejected, or whose save fails, changes nothing, instead of leaving enabled changed.
  • PUT /api/sessions/:sessionId/webhooks/:id answers 400 instead of 500 when url, events, headers, active or retryCount is null.
  • Webhook deliveries drop connection-level custom headers (Connection, Content-Length, Expect, Keep-Alive, TE, Trailer, Transfer-Encoding, Upgrade); Expect, Keep-Alive, Transfer-Encoding, Upgrade or a wrong Content-Length made every delivery fail.
  • Values saved from Dashboard > Infrastructure that contain #, quotes or leading or trailing spaces are written so the next boot reads them unchanged; a database, Redis or S3 password with a # was truncated, which could keep the gateway from starting.
  • Restoring a data backup reloads the cached chat mute, archive and pin state instead of serving and later rewriting the pre-restore values.
  • Switching from external S3 to built-in MinIO creates MinIO with the credentials the restarted gateway uses.
  • With Redis-backed rate limiting, Retry-After reports when the block lifts instead of the full block duration.
  • The REST API, Bull Board, GET /api/health and MCP accept the Bearer scheme in any letter case.
  • PostgreSQL boot migrations no longer inherit DATABASE_STATEMENT_TIMEOUT_MS, so a long migration during an upgrade is not cancelled at 30 seconds.
  • PUPPETEER_ARGS keeps a comma inside a flag value, such as --window-size=1280,720.
  • Boot validation catches MAIN_DATABASE_NAME pointing at the data database's default SQLite file.
  • The production weak-secret refusal names S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY instead of the legacy variable names.
  • Boot fails on a non-integer STORAGE_EXPORT_TTL_MS or STORAGE_EXPORT_SWEEP_MAX_AGE_MS and on a timer above 2147483647 ms for STORAGE_EXPORT_TTL_MS, MESSAGE_REAPER_INTERVAL_MS, WEBHOOK_RECONCILE_INTERVAL_MS and INGRESS_RECONCILE_INTERVAL_MS; such values deleted export archives within milliseconds or fired every millisecond.
  • Updating an automation rule with a null name, replyText, cooldownSeconds or enabled, or an integration instance with enabled: null, answers 400 instead of 500.
  • GET /api/audit answers 400 for a repeated or unknown action or severity, and breaks createdAt ties by id so pages never overlap.
  • The top-chats statistics no longer label a group with one member's name; a group's chatName is null.
  • Automation rules without a kind condition no longer auto-reply to channel posts, broadcast-list messages or status updates.
  • scripts/restore.sh works on the compose named volume and the Helm PVC: OPENWA_RESTORE_SNAPSHOT_DIR moves the pre-restore snapshot off the read-only container root, and the restore runbook gives the in-image commands.
  • scripts/restore.sh takes every pre-restore snapshot before it writes anything, and copies what a symlinked data dir points at; the snapshot of a symlinked data dir was a link to the live data the restore then overwrote.
  • scripts/restore.sh restores a state directory that is its own mount point in place, instead of emptying it and then failing to re-create it.
  • scripts/restore.sh removes leftover SQLite -wal, -shm and -journal files before restoring a database; a stale write-ahead log brought the replaced rows back.
  • scripts/restore.sh places databases, sessions, media and plugins where the archive's .env.generated points, since that file replaces the target's.
  • scripts/backup.sh archives a symlinked sessions, Baileys, media or plugins directory by its content, and scripts/restore.sh refills it through the link instead of replacing the link.
  • The Helm chart renders whole-number env and secretEnv values from a values file as integers instead of exponent notation such as 5.24288e+07.
  • scripts/backup.sh and scripts/restore.sh read .env lines with CRLF endings or spaces around = as the app does, follow the app's fallback from a leftover STORAGE_LOCAL_PATH=./uploads to ./data/media, handle the admin key at BOOTSTRAP_KEY_FILE, and warn about missing media and legacy ./plugins code the archive does not carry.
  • scripts/restore.sh stops before replacing any database when a target it would write cannot be written, instead of leaving a half-restored install.
  • The bundled compose files forward DATABASE_SSL and DATABASE_SSL_REJECT_UNAUTHORIZED, so TLS to a managed PostgreSQL set in .env takes effect.
  • docker-compose.dev.yml publishes the API on API_PORT, as .env.example documents.
  • Dashboard Chats no longer shows the previous session's chat list when that list arrives after a session switch, and changing the UI language no longer resets Chats to the first session or closes the open chat.
  • Dashboard Chats: a message sent or received while a chat's first page is loading no longer disappears when the page lands, and text typed next to an audio attachment stays in the input instead of showing as sent, since audio carries no caption.
  • Dashboard Chats offers no reply, react or delete on a message that is not sent yet, which could only fail.
  • Dashboard Chats lists a chat as soon as its first message arrives instead of after a reload, and a failed background refresh of the chat list keeps the list on screen instead of emptying it.
  • Dashboard Chats: a document's caption shows in its bubble, a file the browser cannot type is sent as a document instead of failing with 400, sends without a message id no longer replace each other, a send that finishes after a session switch no longer reorders the other session's list, and a search result in a session that is not connected shows a warning instead of an empty, mislabeled session.
  • Dashboard: message types and chat kinds read as words in the selected language in the reply banner and quote, the chat list, the messages-by-type chart, and the webhook filter tags and summary; a chat whose newest message is media no longer reads "No messages yet".
  • Dashboard: the media viewer saves an image under its file name instead of its caption, and the chat header shows the right country code for three-digit codes and for +7 numbers.
  • Dashboard: the audit CSV export retries a briefly throttled page, exports at most the newest 10,000 rows with a warning when there are more, keeps the rows fetched so far and asks to wait when the gateway keeps throttling the walk, and shows an error instead of quietly downloading only the current page.
  • Dashboard: Infrastructure holds its form until the saved config has loaded, so Save can no longer overwrite the stored database, S3 and engine settings with defaults, and saving a built-in Postgres or Redis no longer stores a password the bundled container never receives.
  • Dashboard: clearing a plugin instance's session scope returns it to all sessions instead of silently keeping the old scope, and a per-session plugin override the gateway rejects shows the error instead of "Saved".
  • Dashboard: a newly linked session's card shows its phone and last-active time without a reload, and a slow QR answer no longer reopens a closed QR modal or replaces another session's QR.
  • Dashboard: a failed background refresh keeps the last overview on screen instead of replacing the page with an error, and cancelling a bulk batch in Message Tester no longer flips back to Processing when a progress poll answers late.
  • Dashboard: the new-session form refuses names shorter than 3 characters, and pressing Enter twice no longer sends two create requests.
  • Dashboard: clearing an optional plugin config field clears the stored value, and the API Keys page shows a load or permission error instead of an empty list when the keys cannot be read.
  • Dashboard: clearing a plugin's per-session override shows the Global values, so the next Save override no longer restores the cleared ones.
  • Dashboard: turning off a built-in Postgres, Redis or MinIO on the Infrastructure page and restarting stops its container.
  • Dashboard Sessions: an older list response no longer overwrites a newer status, the session detail modal follows status changes while open, and a pairing code no longer lands in a modal opened for another session.
  • Dashboard: double-clicking Create no longer registers a webhook twice, an audit export whose search matches nothing says so, and call, order and product slices in the messages-by-type chart get their own colors.
  • Dashboard: operator and session-scoped keys can post a status and open Channels, which depended on the admin-only engine route.
  • Dashboard: Infrastructure keeps the form and the restart dialog on screen when a background status refresh fails, Templates and Message Tester report a failed sessions or groups read instead of an empty state, and the webhook Create button stays disabled until a session and URL are set.
  • Dashboard: a search hit from another session opens that session's chat, a failed 'load more' in global search keeps the results already shown, a session status push right after a create or delete no longer drops or revives a card, and the audit export no longer repeats a row written during the walk.
  • Dashboard: relative times use the singular or plural the count needs ("1 hour ago"), and Arabic status counts of 100 or more use the grammatical singular.
  • Dashboard: the page search boxes show keyboard focus, the Infrastructure storage badge is translated, and the signed-in role is kept per browser tab with its API key instead of in storage shared by every tab.
  • Dashboard: the session proxy settings are translated in 11 locales and the partial-export media warning in 10, together with the proxy Save button and the webhook chat-kind filter, the Telugu status composer and channel messages are translated, and counts that are exact multiples of a million render in the plural in French, Spanish, Italian and Portuguese.
  • Dashboard: the Templates sidebar item is translated in Arabic, Hebrew, Telugu and both Chinese locales.
  • JavaScript SDK: requests no longer fail with "Illegal invocation" in browsers and Workers, with or without an injected fetch; timeoutMs 0 or Infinity turns the timeout off instead of aborting every request after 1 ms, and a value that is not a non-negative number (an empty variable, 30s) throws a TypeError; and an error body without the usual envelope is shown as JSON instead of [object Object].
  • SDKs: a caller header that differs only in letter case from X-API-Key or Content-Type no longer goes out next to the SDK's own value.
  • PHP SDK: the readiness 503 throws OpenWAServiceUnavailableException instead of an "Array to string conversion" error, and an http:// base URL is logged with error_log() instead of raising a PHP warning; allowInsecureHttp silences it.
  • Java SDK: CatalogProduct.price is a nullable Double, so a product without a price no longer breaks catalog.products().
  • Go SDK: a 429 or 503 whose Retry-After outlasts the remaining timeout is returned at once instead of sleeping into a TimeoutError.
  • Go SDK: CatalogProduct.Price is nil for a product without a price instead of reading as 0.
  • Go SDK: the opt-in retry policy does not retry a 429 whose body carries code: "SEND_PACING_LIMITED".
  • PHP SDK: the insecure-http warning fires for an upper-case HTTP:// base URL and no longer fires for http://LOCALHOST.

Documentation

  • An n8n example forwards incoming WhatsApp messages to a Discord channel with n8n's built-in Webhook and HTTP Request nodes, without the OpenWA community node. Thanks @probably-ABHINAV.
  • The API reference covers engineLoaded on multi-node deployments, the new 403, 404, 429 and 501 answers, the connection headers dropped from webhook deliveries, the WebSocket subscribe limits, sessionScope: null on instance PATCH, optional product price and currency, the production validation error body, the webhook delivery id and idempotency key shapes, node-local stats, and pinned install and catalog URLs.
  • The webhook signature snippets return false on a missing or malformed signature header instead of throwing.
  • README and the migration guide say a compose profile only starts its container and list the settings that point OpenWA at it, and the migration guide's permission advice, legacy PostgreSQL steps and rollback pointer are corrected.
  • The troubleshooting guide no longer suggests running the container as a non-root user, maps a session that is not ready to 400 or 409, and no longer says the shipped compose file includes a TLS proxy; the n8n guide reads delivery-failure rows correctly.
  • The engine capability matrix says ENGINE_TYPE selects one engine for the whole deployment and records the Baileys delete fallback, the Baileys status revoke 403 and the whatsapp-web.js 501 refusals.
  • The scaling guide says which session list and stats fields reflect the answering node, and the plugin guide documents the recipient chatId in bulk send hook input and the install pin rule.
  • The SDK READMEs and SDK reference create a session and pass its id, not its name, and .env.example says a malformed or non-positive value for a boot-validated setting stops the gateway.
  • The maintenance runbook and the backup FAQ back up inside the container under the production compose, the version rollback reloads a PostgreSQL data store from the pre-upgrade dump, and the migration guide covers an upgrade from an image without scripts/backup.sh.
  • The rollback and restore runbooks load a PostgreSQL dump into an empty database inside the built-in openwa-postgres container, and the backup docs keep the password out of DATABASE_URL and say the local media directory is archived under STORAGE_TYPE=s3 too.
  • The SDK READMEs name the transient statuses, and the Go README states that a POST or PATCH is never retried after a network error and only on 429 or 503.
  • The SDK docs say the rate limiter's 429 carries its delay only in Retry-After and that a SEND_PACING_LIMITED refusal waits retryAfterSeconds, and describe engineLoaded on multi-node gateways; the metrics reference lists its 401, 404 and 429 in one place; the backup runbook and script headers give the real path order; and the upgrade hazards cover PostgreSQL upgrades from 0.19.0 to 0.21.x images, which ship no pg_dump.
  • The migration guide's SQLite-to-PostgreSQL script copies chat_states and webhook_outbox_events, the storage export download field is documented as a server-side path, the retention docs cover webhook_outbox_events, live chat history is documented as oldest first, and the GET /api/infra/engines example shows real feature tokens.

Upgrade notes (behavior changes)

  • Baileys: DELETE /api/sessions/:sessionId/status/:id answers 403 for a status this session did not post in the last 24 hours (one posted from the phone or from another node, or before the session's engine was last created by a restart, a stop and start, or a reconnect the gateway runs itself), because its recipients are unknown; it answered 200 without revoking anything.
  • whatsapp-web.js: a reply, location or contact card to a channel or a status/broadcast list, and a poll or sticker to a status/broadcast list, answer 501 where they answered 500; nothing is sent, and the send breaker no longer counts them.
  • whatsapp-web.js: adding a label id the account does not have answers 404 where it answered 200.
  • POST /api/sessions refuses a config that is not a JSON object with 400.
  • Boot fails on a DATABASE_TYPE, ENGINE_TYPE or STORAGE_TYPE with surrounding whitespace; on a malformed, zero or negative value for the chat-media, status and S3 re-probe settings (a negative CHAT_MEDIA_ARCHIVE_TTL_DAYS; 0 still means keep forever); and on a timer above 2147483647 ms for the chat-media and status orphan sweeps, S3_REPROBE_INTERVAL_MS and MEDIA_CONVERSION_TIMEOUT_MS. Boot and migration:run also fail on SQLite when MAIN_DATABASE_NAME names the data database file, including the default ./data/openwa.sqlite when DATABASE_NAME is unset; point one of them at a separate file. migration:run applies the same DATABASE_TYPE rule.
  • whatsapp-web.js: button, list and template-button replies are type text instead of unknown in webhooks, storage and message-type filters, and stored rows with the old tokens are rewritten at boot. Rows earlier releases stored as unknown keep that type.
  • Baileys: reply, quoted send, forward, react, edit, star, pin, unpin and click-button on a message deleted for everyone answer 404 where they answered 200.
  • Java SDK: CatalogProduct.price is a nullable Double instead of a double; recompile against it and check for null before unboxing. JavaScript SDK: CatalogProduct price, currency and priceFormatted are optional.
  • Go SDK: CatalogProduct.Price is a *float64 instead of a float64; check for nil before dereferencing.
  • An automation rule that should answer channel, broadcast-list or status messages needs an explicit kind condition; a rule without one skips those chats.
  • scripts/restore.sh refuses an archive whose state directory was stored as a symlink by an older backup.sh; take the backup again with this version.
  • With BOOTSTRAP_KEY_FILE set, scripts/restore.sh writes the admin key there and refuses to start when that path cannot be written.
  • Baileys: delete for everyone on a message the account cannot revoke now deletes it, with its media, for the account only and still answers 200; it used to change nothing.
  • Sessions deleted on 0.23.5 or 0.23.6 left their auth directories on the data volume, and upgrading does not remove them: delete the directories under the session data path and the Baileys auth dir whose session id no longer exists, drop backups that carry them, and remove the device under Linked Devices on the phone.
  • GET /api/plugins/:id/health reports a sandboxed plugin that is disabled or whose worker crashed as unhealthy, so health-based alerting also fires for a plugin disabled on purpose.
  • NODE_URL keeps its path when a request is forwarded, so the path must be only a reverse-proxy prefix: a NODE_URL ending in /api now forwards to /api/api/....
  • With QUEUE_ENABLED=true, ingress job ids change format. Let pending ingress rows drain (no row pending for INGRESS_RECONCILE_GRACE_MS) before upgrading, or a delivery replayed across the upgrade can be dispatched twice.
  • Integration instances created without a verifyToken before this release still have none; recreate one to get a generated token.
  • Compose installs whose .env came from a template older than 0.18 may still carry uncommented DATABASE_SSL=false and DATABASE_SSL_REJECT_UNAUTHORIZED=true. Compose now forwards both, so they override TLS settings saved in the dashboard; comment them out or delete them. The gateway logs a warning at boot when a forwarded value differs from the one saved in the dashboard.
  • A WebSocket subscribe whose sessionId is not * or a session id of at most 128 safe characters answers INVALID_SESSION, and a connection can hold at most 4096 subscriptions (TOO_MANY_SUBSCRIPTIONS).
  • GET /api/metrics answers 429 after 10 failed token attempts from one client within a minute.

Security

  • Baileys: an inbound edit, revoke or reaction that targets a stored message from another chat, or an edit or 1:1 revoke from someone other than the author, is dropped. Any contact who knew a message id could rewrite or erase the stored copy of a message and trigger message.edited or message.revoked for it. The check needs the original in the Baileys message store, so it does not cover messages imported by the history sync at link time or older than the newest BAILEYS_MESSAGE_STORE_LIMIT messages, and a group revoke is checked for the chat only, since an admin may revoke anyone's message.
  • Baileys: a message retry request is no longer answered with a stored message from a different chat; a requester whose lid neither the session nor Baileys can map to a phone number is still answered.
  • A WebSocket subscribe with an oversized sessionId retained about 20 MB of room names per frame, so any valid API key could exhaust the gateway's memory; the id is now validated and subscriptions per connection are capped.
  • Baileys: link previews scan the fetched page in linear time; a crafted page could stall the whole process for over a minute.
  • GET /api/metrics bounds failed METRICS_TOKEN attempts per client, so the token can no longer be guessed at full speed.
  • Plugin log metadata can no longer overwrite a log line's level, context, message, timestamp or trace.
  • Backup archives under ./backups and restore snapshots (*.pre-restore-*) are ignored by git and kept out of the Docker build context; they hold the admin API key, WhatsApp credentials and database copies.
  • Deleting a session removes its whatsapp-web.js and Baileys auth directories; they stayed on the data volume, and in every later backup, still linked to the WhatsApp account.
  • Baileys: a text status containing a URL no longer reaches the library's own link-preview fetcher.
  • The audit CSV export quotes a bare carriage return and neutralizes cells that start with a tab or carriage return as formula triggers.

Don't miss a new OpenWA release

NewReleases is sending notifications on new releases.