Skip to content

Releases: rmyndharis/OpenWA

v0.7.13

29 Jun 13:16

Choose a tag to compare

Fixed

  • Bulk batch ids are unique per session, not globally. A batch id claimed by one session no longer prevents another session from using the same id — the uniqueness constraint is now scoped to (session, batchId), matching the per-session lookup, so an explicit cross-session reuse no longer fails with a 500. Reusing an id within the same session is still rejected with a clear 400. Existing databases are migrated in place. (#531)
  • A message arriving while a session is being deleted is no longer persisted as an orphan. The inbound-message handler re-checks that the session is still live after its asynchronous processing, so a message that races a session deletion can't leave behind a messages row (which has no cascade) for a session that no longer exists. (#531)
  • Per-session stats return a consistent lastActive timestamp on SQLite and PostgreSQL. GET /stats/sessions/:id previously emitted a different topChats[].lastActive format depending on the database (an ISO date-time on PostgreSQL versus the stored text on SQLite); it is now formatted to a stable YYYY-MM-DD HH:MM:SS on both. (#533)
  • The uuid id default now works on PostgreSQL 12 and older. Id generation relies on a gen_random_uuid() column default, which is a core built-in only from PostgreSQL 13; on older servers it lives in the pgcrypto extension. The migration now enables pgcrypto first, so a fresh deploy against PostgreSQL ≤ 12 no longer fails on startup or first insert. (#533)
  • The audit-log listing no longer loads the whole table for a large limit. GET /audit clamps its page size to a maximum of 200, so an oversized limit can't pull the entire audit_logs table into a single response. (#536)
  • Migration reverts are idempotent on a synchronize-bootstrapped database. The baileys_stored_messages and webhook_delivery_failures migrations now drop their indexes with IF EXISTS, so a down() no longer errors when the named indexes were never created. (#536)
  • Bulk send always releases its in-flight marker. A batch whose session engine was missing, or that threw mid-processing, previously left a stale entry in an in-memory tracking map; the marker is now released on every exit path. (#536)

Security

  • Hook re-entrancy is now blocked for sandboxed plugins too. A plugin running in the worker-thread sandbox could re-fire the hook it was handling by issuing a capability call (for example, sending a message from within a message:sending handler), because the re-entrancy guard did not span the worker boundary — looping the event back into the plugin without bound. The host now runs each worker-initiated capability call inside the in-flight hook context, so such a re-fire is short-circuited exactly as it already was for in-process plugins. (#532)
  • Docker container teardown is constrained to OpenWA-managed services. The POST /infra/restart endpoint passed its profilesToRemove list straight to container removal, which resolved containers by a name substring — so an unrecognized or empty profile could stop and remove an unrelated container. Teardown is now restricted to the managed allowlist (postgres, redis, minio) and container resolution requires an exact openwa-<service> name match. (#534)
  • Failed API-key authentication attempts are now recorded in the audit log. Rejected or denied keys (invalid, disabled/expired, IP- or session-scope-denied, or insufficient role) previously left no audit entry; the gateway now logs an api_key_auth_failed event with the client IP, method, path, and reason, giving administrators a forensic trail for credential probing. Audit logging stays best-effort and never affects the request outcome. (#535)
  • The SSRF guard blocks the deprecated IPv6 site-local range (fec0::/10). Webhook and server-side media URLs are now rejected when they resolve into fec0::/10, closing a gap alongside the already-blocked unique-local and link-local ranges. (#536)
  • Session-scoped MCP tools require a session id before authorization. A session-scoped tool invoked without a session id is now rejected, so a session-restricted API key can't be used to drive such a tool against a session outside its scope. (#536)
  • Contact-card vCards are sanitized on both engines. Sending a contact whose name or number contained CR/LF could inject extra vCard fields on the whatsapp-web.js engine; both adapters now build the vCard through one shared sanitizing helper (CR/LF stripped, digits-only waid). (#537)

v0.7.12

29 Jun 09:10

Choose a tag to compare

Added

  • Brazilian Portuguese (pt-BR) locale. The dashboard is now available in Português (Brasil) — all 9 navigation sections, toasts, dialogs, and form labels are translated. Select it from the language picker on the login screen or the sidebar. Thanks @A831ARD0.

Fixed

  • The engine fallback no longer silently starts the wrong engine. If the configured engine (ENGINE_TYPE, e.g. baileys) is unavailable and the legacy direct-creation fallback is reached, it now fails with a clear error instead of silently constructing the whatsapp-web.js adapter. (#527)

Security

  • Application logs redact secret-valued metadata. The values of secret-named log fields (password, secret, token, api-key, authorization, credential, pepper, private-key) are replaced with [REDACTED] before a line is written — defense-in-depth so a stray log statement can't leak a credential. (#527)

Performance

  • Failed media sends and completed bulk batches no longer retain their base64 payload. A failed media send kept its (often multi-MB) base64 in the message row, and a completed bulk batch kept every message's base64 in message_batches indefinitely — both are now stripped (mimetype/filename kept), so the messages and message_batches tables don't grow without bound. (#524)
  • The dashboard chat view no longer caches full media base64. Chat history is fetched without media and the per-chat cache is evicted sooner, so browsing several media-rich chats no longer risks OOMing the tab; older history media shows a 📎 Media placeholder and recent media still renders. (#525)

v0.7.11

29 Jun 05:34

Choose a tag to compare

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 @lid chat 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_MESSAGES env var (default true). Set to false to skip persisting and dispatching incoming disappearing messages (those with ephemeralDuration > 0) — no DB insert, no webhook dispatch, no websocket event. Backward compatible; existing deployments are unaffected. The ephemeralDuration field is also surfaced on IncomingMessage for 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_failures table 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 messages and message_batches tables had no cascade from sessions, 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-Id headers could diverge from the signed payload when a webhook:before plugin returned a modified payload, and all webhooks for an event shared one data object. Each webhook now receives an isolated copy of the data and the server-generated identifiers are authoritative. (#512)
  • POST /auth/validate no longer double-counts key usage and now validates IP-restricted keys correctly (it previously reported a valid IP-pinned key as invalid). (#507)
  • ⚠️ GET /settings now 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 batchId uniqueness 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 0 for the rate-limit limits and the webhook timeout (behavior change) — values that silently disabled throttling or aborted every delivery. A deployment that set 0 to 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 .env and 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)

v0.7.10

28 Jun 15:55

Choose a tag to compare

Added

  • WhatsApp Status posting (Baileys only). The three status send-* endpoints now post to the status feed on the Baileys engine: POST /api/sessions/:id/status/send-text, /send-image, and /send-video accept a required recipients[] body field (1–256 JIDs, each @c.us or @lid; passed to the engine as statusJidList — an empty array is rejected with 400). Image/video take an optional image.mimetype / video.mimetype; the service defaults to image/jpeg / video/mp4. A whatsapp-web.js session returns 501: WA Web removed WAWebStatusGatingUtils.canCheckStatusRankingPosterGating around 2026-04-30, so the wwebjs path is upstream-blocked. @c.us recipients are reliable; @lid is best-effort (unverified), and the posting account's own phone may briefly show a "waiting for this status update" notice while recipients view it normally. Thanks @CharlesLightjarvis for the report. (#455)

  • Visible placeholder for skipped inbound media. When MEDIA_DOWNLOAD_ENABLED=false (or a media item is over the byte cap), an incoming media message now carries an omitted marker and the dashboard chat renders a 📎 Media placeholder instead of a bare timestamp. The marker reuses the existing { mimetype, omitted, sizeBytes } shape on both the whatsapp-web.js and Baileys engines, so webhook/n8n/dashboard consumers see one consistent contract for "media was present but not downloaded." Thanks @spidgrou. (#501)

Fixed

  • Status image/video no longer hardcode image/jpeg / video/mp4. The SendImageStatusDto / SendVideoStatusDto media input now accepts an optional mimetype; the service applies mimetype ?? 'image/jpeg' (or 'video/mp4') instead of always passing the hardcoded value to the engine. (#455)

  • Clean install on Node 22+ / npm 11. @nestjs/websockets is now declared as a direct dependency — it was only resolving transitively via @nestjs/platform-socket.io, so stricter installs failed with TS2307: Cannot find module '@nestjs/websockets'. The postinstall script also no longer triggers Node's DEP0190 deprecation: shell: true is retained (so Windows still resolves npm via npm.cmd) but the command is now passed as a single string instead of an args array. Thanks @abdullah4tech. (#500)

Changed

  • Italian translation update. Improved the messageTester page title in the Italian (it) dashboard locale to use natural Italian instead of an anglicism. Thanks @albanobattistella. (#497)

v0.7.9

28 Jun 07:51

Choose a tag to compare

Added

  • Bounded list pagination. GET /sessions and GET /webhooks (and the matching agent tools) now accept limit (1–1000, default 1000) and offset query parameters, so large deployments can page through results instead of receiving an unbounded list. (#496)
  • Concurrent-session cap. New MAX_CONCURRENT_SESSIONS env (default 0 = unlimited) caps how many WhatsApp engines may run or initialize at once, protecting memory/Chromium-constrained hosts. (#496)
  • Configurable Redis connect timeout. New REDIS_CONNECT_TIMEOUT_MS (default 5000) bounds how long the queue and cache connections wait when reaching Redis. (#496)

Fixed

  • Webhook delivery during a Redis outage. The webhook queue producer now fails fast instead of buffering indefinitely when Redis is unreachable, falling back to direct (signed, idempotent) delivery; the queue Worker keeps its offline queue so it still tolerates brief reconnects. (#496)
  • Accurate session stats at scale. GET /sessions/stats aggregates status counts in the database, so totals stay correct on deployments with more sessions than the list cap. (#496)
  • Plugin storage key safety & portability. Plugin storage keys are validated and encoded to filesystem-safe filenames (JID-style keys now work on Windows), with backward-compatible reads/deletes of pre-existing files. (#496)

Changed

  • Refreshed project documentation, roadmap, and testing strategy against the current baseline. (#496)

v0.7.8

28 Jun 06:24

Choose a tag to compare

Added

  • Optional inbound-media skip. New MEDIA_DOWNLOAD_ENABLED flag (default true) lets operators skip downloading inbound media entirely on both the whatsapp-web.js and Baileys engines — useful for text-only or low-resource deployments. When disabled, inbound messages omit the media field and report hasMedia: false in webhooks and the dashboard. Thanks @spidgrou. (#492)

Fixed

  • External-S3 setups no longer silently fall back to local disk after upgrading: docker-compose.yml again forwards the legacy S3_ACCESS_KEY / S3_SECRET_KEY (alongside the canonical S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY) so an existing .env keeps reaching the container, and the legacy names are blank-cleared so they can't shadow the dashboard config. (#488 follow-up)
  • The production default-secret guard no longer skips a weak credential for a host-pinned external datastore just because the built-in flag is set: the built-in exemption now requires both the *_BUILTIN flag and an internal host (postgres / minio), so an external Postgres/MinIO with a default password is still rejected in production. (#488 follow-up)
  • The Infrastructure page now shows an error + retry (instead of an editable form seeded from defaults) when the live /infra/status can't be loaded, so a save can no longer flip a running built-in database/Redis/storage to external+empty. (#488 follow-up)
  • /infra/status no longer blocks on the WhatsApp Web version registry fetch, and that fetch is rate-limited after a failure, so a firewalled/offline host no longer stalls up to 5s on every status poll and every session start/reconnect. (#488 follow-up)
  • A replayed message.sent WebSocket echo no longer downgrades a chat message already shown as delivered/read; the live-append path now applies the same forward-only delivery-status merge as the ack path. (#484 follow-up)

Changed

v0.7.7

27 Jun 18:46

Choose a tag to compare

Added

  • Dashboard chat thread UX: URLs in messages are now clickable links, WhatsApp text formatting (bold/italic/strikethrough/monospace) renders, images open in a photo lightbox, and the scroll position is remembered per chat. Thanks @softronicve. (#484)
  • The Infrastructure page now shows the actual WhatsApp Web build the whatsapp-web.js engine is using (e.g. 2.3000.1042251103-alpha) and how it was chosen (pinned via WWEBJS_WEB_VERSION, auto-resolved, or native), surfaced via /infra/status. The engine card previously showed only the npm library version (whatsapp-web.js 1.34.7), which is unrelated to the WA Web build that actually governs connection stability. (#488)
  • Infrastructure data backup & restore: export all Data-DB tables to a JSON file and import them back, wired into the database-switch flow. When you change the database backend, the restart dialog now warns that the new database starts empty and offers a one-click backup before switching; a storage switch warns that existing media is not moved. (#488)
  • The Infrastructure page flags any database/redis/storage setting that is pinned by an environment variable (its running value differs from the saved config), so it's clear a dashboard change won't apply until that variable is unset, instead of the control silently having no effect. (#488)
  • The storage card now warns when S3 is selected but unreachable (a dead/misconfigured bucket no longer shows a misleading green badge), via a new s3Available field on /infra/status; the check re-probes (throttled) rather than latching the boot-time result, so a bundled MinIO that comes up after the app self-corrects. A backup import that exceeds the request size limit now reports an actionable message (raise BODY_SIZE_LIMIT) instead of a bare "Payload Too Large". (#488)
  • Data-loss & availability hardening for the new infra flows: importing a backup now refuses an empty/garbage file (it no longer wipes the database and reports success) and asks for confirmation first; selecting the built-in Postgres/MinIO no longer crash-loops a production boot on the default-secret guard (the bundled containers run on the internal-only network); and a transient failure fetching the WhatsApp Web version is no longer cached, so it retries instead of permanently falling back. (#488)
  • Human-readable console logs: the LoggerService now renders a colorized, NestJS-style line ([OpenWA] <pid> - <timestamp> <LEVEL> [Context] <message> with dimmed key=value metadata and stack traces on their own line) instead of always emitting raw JSON, so application logs line up visually with NestJS's own framework logs. The format defaults to structured JSON in production (NODE_ENV=production, for containers and log aggregators) and human-readable pretty everywhere else, and can be pinned with LOG_FORMAT=pretty|json. NO_COLOR / FORCE_COLOR are honored. JSON output is byte-for-byte unchanged when selected. (#469)

Fixed

  • whatsapp-web.js sessions that scanned the QR then immediately disconnected (looping qr → authenticating → disconnected) when no WWEBJS_WEB_VERSION was pinned — the common Docker default. The engine now auto-resolves the current known-good WhatsApp Web build from the wppconnect wa-version registry and pins it, instead of relying on whatsapp-web.js's auto-select which could latch onto an incompatible bleeding-edge build that authenticates but never reaches "ready". WWEBJS_WEB_VERSION=off keeps the old native auto-select; an explicit version still pins exactly. (#488)
  • Dashboard message-analytics charts no longer silently vanish on PostgreSQL: /stats/messages (top-chats) ordered by an unquoted mixed-case alias (ORDER BY messageCount), which PostgreSQL case-folds and rejects with column "messagecount" does not exist (500). It now orders by the aggregate directly, so the query — and the dashboard charts it feeds — work on PostgreSQL as they already did on SQLite. The chart section also shows a clear notice on a real error instead of rendering nothing (it previously treated every error as a non-admin 403 and hid itself). (#488)
  • The Infrastructure page now shows what is actually running for the database, Redis, storage, and engine — the badge/selected card follow the live /infra/status instead of the saved data/.env.generated, which could disagree when a setting is supplied via environment variable. Previously a stack running PostgreSQL via DATABASE_TYPE=postgres showed "SQLite" (the first-run default still in the saved file). /infra/status now also reports redis.enabled. (#488)
  • The "Use Built-in PostgreSQL/Redis/MinIO Container" toggles now reflect whether OpenWA's bundled container is actually running and backing the service (detected from the labeled container + the configured host), not just the saved intent — so a Postgres stack started via the postgres compose profile correctly shows built-in, and a stopped/external one shows off. Falls back to the saved flag when Docker isn't reachable. (#488)
  • Switching away from a built-in backend (built-in → external/disabled) now tears down the bundled container reliably even after a page reload: removal is derived server-side from the saved *_BUILTIN flags + the running labeled containers, instead of only trusting the browser's in-memory list (which reset on reload and left the container orphaned). Named volumes are preserved, so re-enabling reuses the data. (#488)
  • Dashboard "by type" message chart: each message type now gets a stable, distinct color keyed by type name (with a deterministic hash fallback) instead of a rotating array-index palette, so a slice keeps its color when the set of present types changes between requests and types past the eighth no longer collide. (#486)
  • Removed the oversized decorative watermark icons bleeding through the dashboard stat cards. (#488)
  • Dashboard switches for the database, Redis, and storage backends now actually take effect after a restart, matching how the engine switch already worked. The bundled docker-compose.yml forwards these settings blank (${VAR:-}) so the dashboard's saved selection (in data/.env.generated) is honored, while a real value set in your .env/host still pins it (and the UI now says so). Previously compose forwarded concrete defaults that silently shadowed the dashboard's choice, so switching had no effect under Docker. (#488)

Changed

  • ⚠️ docker-compose.yml now forwards the S3 credentials under their canonical names S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY (and adds S3_REGION), matching what the app and dashboard read. The legacy S3_ACCESS_KEY / S3_SECRET_KEY are still accepted as a fallback, so existing setups keep working, but updating your .env to the canonical names is recommended. (#488)
  • ⚠️ Database/Redis/storage selection is now sourced from the dashboard-managed data/.env.generated when not pinned by an environment variable (see Fixed, above). If you previously relied on the compose file's concrete defaults overriding a stale data/.env.generated, set the value explicitly in your .env/host to pin it. First-run defaults (SQLite, local storage, Redis off) are unchanged. (#488)

v0.7.6

26 Jun 09:02

Choose a tag to compare

Changed

  • CI now runs the dashboard unit tests, and re-runs the client-SDK suites when a server DTO or the engine interface changes (not only on SDK edits), so contract drift is caught at its source. (#478)
  • The Postgres connection pool now applies query/connection timeouts (statement_timeout, idleTimeoutMillis, connectionTimeoutMillis) on the runtime connection, so a stuck query or a saturated pool fails fast instead of hanging requests. The migration connection keeps idle/connection timeouts but never statement_timeout, so a long CREATE INDEX is not aborted. Env-tunable (DATABASE_STATEMENT_TIMEOUT_MS, DATABASE_IDLE_TIMEOUT_MS, DATABASE_CONNECTION_TIMEOUT_MS), conservative defaults, 0 disables; SQLite is unaffected. (#480)

Fixed

  • A plugin whose enable failed after it had already subscribed hooks no longer leaves stale hook registrations behind; a later successful enable could otherwise dispatch each event to the plugin more than once. (#477)
  • The WebSocket message.ack event now carries the same { id, messageId, status, ack } shape over the socket as the matching webhook does — the socket previously omitted id and the legacy ack. (#477)
  • Reconnect timers are no longer stacked when two disconnects arrive back-to-back, and a terminal engine failure now cancels any pending reconnect so a FAILED session cannot be resurrected by a stale timer. (#477)
  • The dashboard recovers from a stale lazy-loaded chunk after a redeploy with a single guarded reload instead of replacing the whole UI with the error screen; the Content-Security-Policy img-src now allows blob: so the outgoing image-attachment preview renders. (#477)
  • The Baileys engine's number-check (GET /sessions/:id/contacts/check/:number) now returns a neutral <phone>@c.us id, matching the whatsapp-web.js engine, instead of a raw @s.whatsapp.net id. (#477)
  • The data export/import now includes the lid_mappings resolution cache, so a backup/restore or a SQLite↔PostgreSQL migration no longer drops it. (#477)
  • The JavaScript client SDK applies the JSON Content-Type and X-API-Key after caller-supplied headers, so they can no longer be overridden by defaultHeaders (matching the Python and PHP SDKs); an unfollowed redirect (HTTP status 0) now raises a clear error instead of OpenWA API 0. (#478)
  • The infrastructure status endpoint reports the active S3 bucket when storage is in S3 mode, instead of only the unused local media path. (#478)
  • The migration CLI now honors the dashboard-written data/.env.generated, so migration:run:prod targets the configured database (e.g. PostgreSQL) instead of silently defaulting to SQLite. (#479)
  • The first-run generated config writes STORAGE_LOCAL_PATH (the key the backend reads) instead of the dead STORAGE_PATH. (#479)
  • The Sessions page now keeps the shared dashboard cache in sync, so creating/stopping/deleting a session no longer leaves the Dashboard showing stale session counts or status until a refresh. (#479)

Security

  • The startup banner prints the full admin API key only when it is first created; on subsequent boots the key is masked, so the live credential is not re-written to the log pipeline on every restart. (#478)
  • The production secret guard now rejects a placeholder REDIS_PASSWORD (e.g. changeme); an empty/unset password is still allowed so passwordless private-network Redis continues to boot. (#478)
  • The published PHP SDK package no longer ships its test suite, PHPUnit config, or composer.lock. (#478)
  • The production weak-secret guard now also rejects the common defaults 123456, qwerty, root, test, and demo. Matching stays an exact full-value comparison, so a strong secret that merely contains one of these words is not blocked. (#480)
  • The gateway now logs a startup warning when API_KEY_PEPPER is unset in production (stored API-key hashes then use plain SHA-256). Advisory only — enabling a pepper invalidates existing key hashes, so it stays opt-in and is never enforced. (#480)

v0.7.5

26 Jun 00:45

Choose a tag to compare

Fixed

  • The stats/analytics endpoint no longer crashes on PostgreSQL. The message time-series query grouped by an output alias named timestamp — a reserved type keyword in PostgreSQL — so GROUP BY timestamp was not read as the alias and the query failed with "column m.createdAt must appear in the GROUP BY clause" (SQLite tolerated it, so unit tests on the SQLite test DB never caught it). The alias is now bucket; the API response field is unchanged. (#474)

Documentation

  • Added a Traefik / Coolify reverse-proxy guide to the troubleshooting FAQ: WebSocket forwarding, the docker-proxy double-hop that causes intermittent 504s behind Coolify (held-open Socket.IO connections exhausting the pool to the single-port upstream), and idle-timeout tuning. (#467)

v0.7.4

25 Jun 16:14

Choose a tag to compare

Fixed

  • WebSocket events are now delivered exactly once to a client subscribed to overlapping rooms (for example both a specific event and the * wildcard for the same session). The real-time fan-out previously sent one copy per matching room, so such a client could receive the same event two to four times. The bundled dashboard was unaffected (its pages subscribe to disjoint rooms); this fixes duplicate delivery for custom WebSocket clients. (#468)
  • session.authenticated and session.disconnected are now emitted over the WebSocket (with { phone, pushName } and { reason } respectively), matching the existing webhook payloads. They were advertised as subscribable but were only ever delivered via webhooks, so socket subscribers never received them. (#468)
  • The infrastructure status endpoint (GET /api/infra/status) now reports the actual media storage path — it reads storage.localPath (default ./data/media), the key the storage service uses — instead of a non-existent storage.path key that always reported ./uploads. (#472)
  • The JavaScript client SDK's timestamp fields (MessageResponse, MessageRecord) are documented as Unix seconds (the real passed-through value, previously mislabelled milliseconds), and the PHP SDK's Client::request() is correctly typed (mixed $body): mixed). (#472)

Changed

  • The WebSocket group.join / group.leave / group.update events are no longer accepted as socket subscriptions — they have no engine source and were never delivered on the socket. Subscribing to one now returns a clear validation error instead of silently never delivering. They remain reserved on the webhook side. Webhook subscriptions are unaffected. (#468)

Documentation

  • The docs/ set was reconciled against the v0.7.3 implementation — API specification and collection, operational runbooks, troubleshooting, system architecture, security, database, dashboard, SDK, and plugin docs — correcting drift accumulated across releases. (#471)