Overview
PassAgent’s notification system delivers security-critical alerts through three channels: in-app toasts, Slack webhooks, and Discord webhooks. The system is split into two layers — a dispatcher for external channels and a preset builder for in-app notifications.In-App
Sonner toast overlays with actionable buttons for OTPs, CAPTCHAs, and breach alerts
Slack
Rich Block Kit messages via incoming webhooks with color-coded severity
Discord
Embed messages via webhooks with timestamps and color indicators
Architecture
Event Types
The dispatcher supports seven event types that map to specific security operations:| Event | Trigger | Color (Slack) |
|---|---|---|
password_changed | Password successfully reset or rotated | Green #36a64f |
breach_detected | HIBP or dark-web monitoring finds a credential match | Red #ff0000 |
security_alert | Unusual login, device mismatch, or policy violation | Orange #ff9900 |
integration_connected | User connects a new Slack or Discord integration | Green #36a64f |
integration_disconnected | Integration removed or token revoked | Gray #999999 |
password_shared | Credential shared via family or group sharing | Indigo #4f46e5 |
password_share_revoked | Shared credential access revoked | Red #dc2626 |
Notification Dispatcher
The dispatcher (lib/notification-dispatcher.ts) sends alerts to all connected Slack and
Discord integrations for a given user. It queries the user_integrations table in Supabase
to find active webhook URLs.
Slack Format
Slack notifications use Block Kit with a header block (emoji + title), a section block (Markdown message), and a context block (“PassAgent Security” + timestamp). Messages are color-coded using the attachmentcolor field per the table above.
Discord Format
Discord notifications use embeds with title, description, decimal color value, a “PassAgent Security” footer, and an ISO 8601 timestamp. Webhook URLs are stored in theaccess_token field as JSON ({ webhook_url: "..." }) with a fallback to refresh_token.
In-App Notification Presets
In-app notifications render as Sonner toast overlays via theIconNotification component.
Eight preset types cover the full lifecycle of a password reset:
Preset Reference
| Type | Title | Color | Actionable | Buttons |
|---|---|---|---|---|
otp_detected | Verification Code Detected | brand | Yes | ”Yes, set password” / “Skip” |
trust_device | Trust This Device? | warning | Yes | ”Trust” / “Skip” |
reset_success | Password Reset Complete | success | No | — (6s auto-dismiss) |
reset_failed | Reset Failed | error | No | — (8s auto-dismiss) |
captcha_needed | CAPTCHA Needs Help | warning | Yes | ”Open Browser” / “Dismiss” |
vault_updated | Vault Updated | success | No | — (5s auto-dismiss) |
breach_detected | Breach Alert | error | Yes | ”Reset Now” / “Dismiss” |
needs_manual | Action Needed | warning | Yes | ”View Details” / “Dismiss” |
Building Notifications with Overrides
ThebuildNotification function merges preset defaults with caller-provided overrides.
It also interpolates {service} placeholders using the metadata.serviceName value.
Integration Setup
Slack
- User creates a Slack app with an incoming webhook.
- The webhook URL is stored in the
refresh_tokenfield of the integration record. - The dispatcher queries for
integration_id = "slack"andconnected = true.
Discord
- User creates a webhook in their Discord server settings.
- The webhook URL is stored as
{ webhook_url: "..." }JSON in theaccess_tokenfield. - The dispatcher queries for
integration_id = "discord"andconnected = true.
Error Handling
The dispatcher catches errors per-integration and continues delivering to remaining channels. Failed deliveries are returned in theerrors array:
| Error | Cause | Resolution |
|---|---|---|
No Slack webhook URL found | Missing webhook in integration record | Re-connect Slack |
Slack webhook failed: 404 | Webhook deleted in Slack | Create new webhook |
No Discord webhook URL found | Malformed access_token JSON | Re-connect Discord |
Discord webhook failed: 401 | Webhook token expired or revoked | Create new webhook |