Skip to content

fix(webhook): deliver session lifecycle events and key them per occurrence#335

Closed
rmyndharis wants to merge 1 commit into
mainfrom
fix/session-webhook-event-delivery
Closed

fix(webhook): deliver session lifecycle events and key them per occurrence#335
rmyndharis wants to merge 1 commit into
mainfrom
fix/session-webhook-event-delivery

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

Problem

Four webhook events — session.qr, session.authenticated, session.disconnected, session.status — are declared in WEBHOOK_EVENTS and accepted at subscription, but no code path ever dispatched them. A subscriber (the n8n trigger node, the dashboard, a custom integration) registers for session.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

  • Wire the four session.* events from their existing engine-callback emit sites in session.service.ts (onQRCodesession.qr, onReadysession.authenticated, onDisconnectedsession.disconnected, and the updateStatus() chokepoint → session.status). Delivery is fire-and-forget (void dispatch(...)), so a webhook failure never disrupts the engine.
  • Per-occurrence idempotency keys for lifecycle events. Lifecycle events recur with identical content (the same phone on every reconnect, a constant 'logged out' disconnect reason, the same status across cycles). The content-hash key collapsed these onto one value, so a consumer deduping on X-OpenWA-Idempotency-Key would 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.
  • No duplicate session.status. Some engines (whatsapp-web.js) signal one transition via both onStateChanged and a dedicated callback, which would POST session.status twice. The webhook now fires only when the status actually changes from the last one sent.
  • Docs corrected. The n8n integration guide now lists the real, firing event names; 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: each session.* event dispatches with the right payload from its emit site; session.status is not double-dispatched when onStateChanged and 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.
  • Full backend suite 738/738; dashboard build + lint clean.

…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).
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
@rmyndharis

Copy link
Copy Markdown
Owner Author

Shipped in v0.4.3 (integrated via the release PR #354 and tagged v0.4.3). Thanks! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant