Notification Events Registry
Catalog of every notification event the platform dispatches — event key, channel defaults, trigger source, and whether each event also fires an outbound webhook. Covers the 12 Wave D events shipped 2026-06-22 (security, money, lifecycle, compliance).
This page lists every eventKey that has a NotificationDefault row + at least one production dispatcher. Use it to know what events the platform fires today, what channels each lands on by default, and where in the codebase the dispatch is wired.
Channel defaults are admin-editable at runtime via PATCH /admin/notifications/defaults/:eventKey. Per-user opt-out lives in NotificationPreference and is enforced last-mile in the worker (see Observability — NotificationJob columns). The defaults below show the seed values shipped in packages/prisma/seed/notifications.seed.ts.
Critical events bypass user preferences. The 6 events in CRITICAL_EVENTS (security_alert, security_alert_token_reuse, account_locked, payout_processed, payout_failed, payout_critical_failure) re-enable email + in_app regardless of per-user opt-out, because the resolveChannels() policy treats them as platform-mandatory. Wave D events are NOT in this set — they respect user opt-out.
Wave D events (shipped 2026-06-22)
12 new events grouped by category. Total NotificationDefault rows: 32 → 44.
Security (5 events)
| Event key | Push | In-app | Web push | Trigger | Wired webhook | |
|---|---|---|---|---|---|---|
2fa_enabled | ✓ | — | ✓ | — | TOTP secret committed (after POST /auth/2fa/verify) | — |
2fa_disabled | ✓ | — | ✓ | — | TOTP secret cleared (after POST /auth/2fa/disable) | — |
oauth_linked | ✓ | — | ✓ | — | OAuth provider linked to existing account | — |
oauth_unlinked | ✓ | — | ✓ | — | OAuth provider unlinked from account | — |
payout_method_changed | ✓ | ✓ | ✓ | ✓ | Creator updates Stripe Connect payout method or preferred-method toggle | ✓ |
Money (3 events)
| Event key | Push | In-app | Web push | Trigger | Wired webhook | |
|---|---|---|---|---|---|---|
refund_processed | ✓ | — | ✓ | — | Stripe charge.refunded webhook handler confirms refund posted | ✓ |
payment_failed | ✓ | ✓ | ✓ | ✓ | Stripe payment_intent.payment_failed webhook (re-keyed from legacy security_alert{action:'payment_failed'}) | ✓ |
chargeback_resolved | ✓ | — | ✓ | — | Stripe charge.dispute.closed webhook (won / lost — UI distinguishes) | ✓ |
Lifecycle (3 events)
| Event key | Push | In-app | Web push | Trigger | Wired webhook | |
|---|---|---|---|---|---|---|
stripe_requirements_added | ✓ | ✓ | ✓ | ✓ | Stripe Connect account.updated webhook reports new requirements.currently_due items | ✓ |
iap_retry_payment_failed | ✓ | — | ✓ | — | Apple IAP verification cron reaches 3rd terminal retry attempt | — |
dmca_takedown_executed | ✓ | — | ✓ | — | Admin executes a DMCA takedown via the trust-safety review flow (reviewDMCA) | — |
Compliance (1 event)
| Event key | Push | In-app | Web push | Trigger | Wired webhook | |
|---|---|---|---|---|---|---|
gdpr_deletion_requested | ✓ | — | ✓ | — | Self-service deletion (POST /users/delete) OR admin-initiated trust-safety deletion. Notifies the user that the erasure grace-period clock has started. | — |
Variable payloads
Every event carries a variables object that the template renderer interpolates into subject / body / push title via the flat \{(\w+)\} regex (no ICU). Below is the canonical shape for each Wave D event — pulled directly from the dispatcher call sites. UI consumers reading NotificationItemDto.data should branch on eventKey rather than checking for individual variable presence.
| Event key | Variables object | Source path:line |
|---|---|---|
2fa_enabled | { enabledAt } | auth/two-factor.service.ts:115 |
2fa_disabled | { actor, disabledAt } — actor = 'you' or 'an administrator' | auth/two-factor.service.ts:190 |
oauth_linked | { provider, linkedAt } | auth/oauth.service.ts:249 |
oauth_unlinked | { provider, unlinkedAt } | auth/oauth.service.ts:288 |
payout_method_changed | { changeType, newMethod, changedAt } — changeType = 'bank' | 'preferred' | 'both' | creator/creator.service.ts:1101–1105 |
refund_processed | { amount, currency, chargeId } — amount is a 2-decimal string, currency uppercased | payment/stripe-webhook.service.ts:413 |
payment_failed | { reason, amount, currency } — reason is the verbose Stripe last_payment_error.message (recipient is the payer, no PII leak) | payment/stripe-webhook.service.ts:627–631 |
chargeback_resolved | { outcome, disputeId } — outcome = 'won' | 'lost' | payment/stripe-webhook.service.ts:573 |
stripe_requirements_added | { requirementsCount, deadlineISO, requirementsList } | creator/stripe-connect.service.ts:389–391 |
iap_retry_payment_failed | { productId, error, platform, attempts } — error truncated to 200 chars | payment/mobile-iap.service.ts:206–212 |
dmca_takedown_executed | { dmcaCaseId, infringingUrl, counterNoticeUrl } | trust-safety/trust-safety.service.ts:747–749 |
gdpr_deletion_requested (self) | { actor: 'you', gracePeriodEnds, requestId, cancelUrl } | user/user.service.ts:777–782 |
gdpr_deletion_requested (admin) | { actor: 'an administrator', gracePeriodEnds, requestId, cancelUrl } | trust-safety/trust-safety.service.ts:575–580 |
Source — dispatcher call sites
The dispatchers below combine two concerns: (a) NotificationService.send writes the in-app / email / push job rows, and (b) WebhookService.dispatchEvent (when wired) fires the outbound webhook to admin-registered subscriber URLs.
| Event | Service | Lines |
|---|---|---|
2fa_enabled | apps/api-core/src/modules/auth/two-factor.service.ts | 113 |
2fa_disabled | apps/api-core/src/modules/auth/two-factor.service.ts | 188 |
oauth_linked | apps/api-core/src/modules/auth/oauth.service.ts | 247 |
oauth_unlinked | apps/api-core/src/modules/auth/oauth.service.ts | 286 |
payout_method_changed | apps/api-core/src/modules/creator/creator.service.ts | 1099, 1111 (webhook wire) |
refund_processed | apps/api-core/src/modules/payment/stripe-webhook.service.ts | 411, 419 (webhook wire) |
payment_failed | apps/api-core/src/modules/payment/stripe-webhook.service.ts | 625, 644 (webhook wire) |
chargeback_resolved | apps/api-core/src/modules/payment/stripe-webhook.service.ts | 571, 579 (webhook wire) |
stripe_requirements_added | apps/api-core/src/modules/creator/stripe-connect.service.ts | 382, 398 (webhook wire) |
iap_retry_payment_failed | apps/api-core/src/modules/payment/mobile-iap.service.ts | 203 |
dmca_takedown_executed | apps/api-core/src/modules/trust-safety/trust-safety.service.ts | 744 |
gdpr_deletion_requested (self-service) | apps/api-core/src/modules/user/user.service.ts | 775 |
gdpr_deletion_requested (admin) | apps/api-core/src/modules/trust-safety/trust-safety.service.ts | 573 |
Source — registry + defaults
| Source | Path | Lines |
|---|---|---|
Seed (NotificationDefault rows) | packages/prisma/seed/notifications.seed.ts | 72–76 (Security), 83–85 (Money), 94–96 (Lifecycle), 102 (Compliance) |
| Prisma model | packages/prisma/prisma/schema.prisma | NotificationDefault (2131), NotificationEvent (1785), NotificationJob (2203) |
| Channel resolver (critical-event policy) | apps/api-core/src/modules/notification/notification.service.ts | resolveChannels() — see Observability for behaviour |
| Outbound webhook dispatch | apps/api-core/src/modules/webhook/webhook.service.ts | 218 (dispatchEvent) |
| Live response | NOT verified — payload variables sourced from dispatcher call sites cited above |
List Web Push Subscriptions
Read the calling user's active web push subscriptions. Returns up to 100 rows ordered newest-first. Public-safe slice — keys NOT exposed.
Observability — NotificationJob columns
How latencyMs + costUsd are populated on NotificationJob rows (Wave E Batch 2). Covers when workers write them, what queries consume them, and why costUsd remains null today.