fix(webhook): deliver session lifecycle events and key them per occurrence#335
Closed
rmyndharis wants to merge 1 commit into
Closed
fix(webhook): deliver session lifecycle events and key them per occurrence#335rmyndharis wants to merge 1 commit into
rmyndharis wants to merge 1 commit into
Conversation
…rence session.qr, session.authenticated, session.disconnected and session.status were declared in the webhook event set but never dispatched, so subscribers (including the n8n trigger node) waited indefinitely. Each is now wired from its engine-callback emit site. To keep a spec-following consumer from collapsing legitimately-distinct lifecycle events on the X-OpenWA-Idempotency-Key, the recurring lifecycle keys (status/authenticated/disconnected) are salted with a per-dispatch occurrence timestamp that stays stable across retries; message keys remain content-based. The session.status webhook is also guarded against firing twice when an engine signals one transition via both onStateChanged and a dedicated callback. The n8n docs are corrected to the real event names (session.authenticated, session.qr) and group.* is marked reserved (accepted on subscribe but not emitted yet).
Merged
rmyndharis
added a commit
that referenced
this pull request
Jun 19, 2026
* fix(webhook): deliver session lifecycle events and key webhook hardening (#335) * fix(security): pin outbound webhook and media fetches to validated IP (#338) * fix(plugins): persist plugin enable/config and restore (#339) * fix(message): persist bulk-sent messages, sanitize SSRF (#340) * fix(engine): return the real id for forwarded messages (#341) * fix(security): harden outbound requests, IPv6 SSRF (#344) * fix(security): secret-file perms, key pepper, allowedIps (#345) * fix(storage): bound tar imports, contain storage keys (#346) * fix(session): reconcile late acks, serialize reactions (#348) * fix(contract): webhook timeout, bounded shutdown, 501 (#350) * feat(session): force-kill a stuck session (#352) * merge #343 * merge #351 * chore(release): v0.4.3 — CHANGELOG, version bump (package.json/dashboard/swagger), README + docs
Owner
Author
|
Shipped in v0.4.3 (integrated via the release PR #354 and tagged |
This was referenced Jun 20, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Four webhook events —
session.qr,session.authenticated,session.disconnected,session.status— are declared inWEBHOOK_EVENTSand accepted at subscription, but no code path ever dispatched them. A subscriber (the n8n trigger node, the dashboard, a custom integration) registers forsession.disconnected, the webhook is stored active, and the events never arrive — silent non-delivery on a documented happy path. The n8n docs compounded it by advertising two names (session.connected,session.qr_ready) that aren't in the event set at all, so they were rejected with a 400 at registration.Changes
session.*events from their existing engine-callback emit sites insession.service.ts(onQRCode→session.qr,onReady→session.authenticated,onDisconnected→session.disconnected, and theupdateStatus()chokepoint →session.status). Delivery is fire-and-forget (void dispatch(...)), so a webhook failure never disrupts the engine.'logged out'disconnect reason, the same status across cycles). The content-hash key collapsed these onto one value, so a consumer deduping onX-OpenWA-Idempotency-Keywould silently drop every recurrence. Recurring lifecycle keys are now salted with a per-dispatch occurrence timestamp captured once and reused across retries — distinct occurrences get distinct keys, retries of the same delivery stay stable. Message keys remain purely content-based.session.status. Some engines (whatsapp-web.js) signal one transition via bothonStateChangedand a dedicated callback, which would POSTsession.statustwice. The webhook now fires only when the status actually changes from the last one sent.group.*is documented as reserved (accepted on subscribe but not emitted yet — no engine source exists).Compatibility
Non-breaking. No declared event was removed and no response shape changed; previously-dead events now deliver, and the idempotency key is an opaque dedup token whose format consumers don't depend on. SemVer: patch.
Tests
session.service.spec.ts: eachsession.*event dispatches with the right payload from its emit site;session.statusis not double-dispatched whenonStateChangedand a dedicated callback report the same status.idempotency.util.spec.ts: recurring lifecycle keys are distinct per occurrence, retry-stable for the same occurrence, and message keys are unaffected by the occurrence salt.