Added
- Disappearing-messages support (Baileys engine). Outbound messages now honor a chat's disappearing-messages timer and set it on each send (text, media, and replies), so recipients no longer see "This message won't disappear — the sender may be using an older version of WhatsApp." The timer is learned from inbound messages — the reliable source, since the cached chat setting is often absent for a long-standing timer — and resolved across both phone and
@lidchat identifiers so it applies on LID-migrated 1:1 chats, with a fallback to the chat's cached setting. It is applied only when a positive value is known; when it's unknown or disabled, the per-message expiration is omitted, exactly as before. Reactions, deletes/revokes, and status posts are unaffected. Thanks @ulises2k. (#473, #513) - Selective skip for disappearing messages. New
STORE_EPHEMERAL_MESSAGESenv var (defaulttrue). Set tofalseto skip persisting and dispatching incoming disappearing messages (those withephemeralDuration > 0) — no DB insert, no webhook dispatch, no websocket event. Backward compatible; existing deployments are unaffected. TheephemeralDurationfield is also surfaced onIncomingMessagefor consumers that want to handle it themselves. Thanks @spidgrou. (#506) - Durable dead-letter record for failed webhook deliveries. A webhook delivery that permanently fails — exhausting its retries or being rejected before it is sent — is now persisted to a new
webhook_delivery_failurestable instead of disappearing when its job is evicted from the queue. Operators can review the recorded failures (endpoint, event, status, error, attempts) through a new admin endpoint,GET /webhooks/delivery-failures. (#520)
Fixed
- Deleting a session now removes its message history and bulk batches. The
messagesandmessage_batchestables had no cascade fromsessions, so a deleted session left its rows behind — growing the largest tables without bound and skewing dashboard statistics. They are now removed in the same transaction as the session. (#504) - Deleting a session while it is reconnecting no longer leaks its engine. A delete that landed during the multi-second engine initialization of an in-flight reconnect (or start) could leave the freshly-launched browser/socket registered under the now-deleted session, still counting toward the concurrent-session limit. The post-init guard now re-checks that the session still exists before keeping the engine. (#521)
- Inbound media downloads are bounded by a wall-clock timeout. A slow or stalled inbound media transfer could hold a download slot — and, on the Baileys engine, the entire inbound-message pipeline — open indefinitely. Downloads now time out (
MEDIA_DOWNLOAD_TIMEOUT_MS, default 30s) and the message is delivered with the media omitted. (#510) - Webhook delivery identifiers stay consistent with the signed body. The
X-OpenWA-Idempotency-Key/X-OpenWA-Delivery-Idheaders could diverge from the signed payload when awebhook:beforeplugin returned a modified payload, and all webhooks for an event shared onedataobject. Each webhook now receives an isolated copy of the data and the server-generated identifiers are authoritative. (#512) POST /auth/validateno longer double-counts key usage and now validates IP-restricted keys correctly (it previously reported a valid IP-pinned key as invalid). (#507)- ⚠️
GET /settingsnow requires an ADMIN key (behavior change) — matching the rest of the configuration surface; it was previously readable by any authenticated key. A client that read settings with a non-admin key must switch to an ADMIN key. (#514) - Bulk-message
batchIduniqueness is scoped per session, so two sessions can reuse a batch id and neither can probe the other's id namespace. (#515) - ⚠️ Boot-time configuration validation now rejects
0for the rate-limit limits and the webhook timeout (behavior change) — values that silently disabled throttling or aborted every delivery. A deployment that set0to disable these must remove the override or use a positive value. (#516) - SSRF protection now blocks the RFC6052 IPv4-translatable IPv6 form (
::ffff:0:a.b.c.d), closing a gap where an internal address could be reached behind a NAT64/SIIT translator. (#518) - Per-key IP allowlist now uses the shared, hardened IP matcher and rejects a malformed client address instead of coercing it into an allowed range. (#519)
- Dashboard: the Infrastructure page is no longer rendered for non-admin roles, and image-attachment preview object URLs are released after use. (#508)
- Released a small in-memory leak: a deleted session's stored failure reason is now cleared. (#505)
- The webhook worker now connects to the configured Redis. Configuration from
.envand the dashboard-saved file is loaded before the application modules are evaluated, so the webhook delivery worker reads its Redis host/port/password from the configured values instead of falling back to a local default when those are supplied by file rather than the process environment. (#523)
Performance
- Configurable webhook worker concurrency (
WEBHOOK_WORKER_CONCURRENCY, default 10): a single slow or unresponsive receiver no longer head-of-line-blocks delivery for every other webhook. (#511) - Dropped a redundant single-column index on
messages(sessionId)already covered by the existing composite indexes, reducing write-time overhead on a high-volume table. (#509)