Skip to main content

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:
EventTriggerColor (Slack)
password_changedPassword successfully reset or rotatedGreen #36a64f
breach_detectedHIBP or dark-web monitoring finds a credential matchRed #ff0000
security_alertUnusual login, device mismatch, or policy violationOrange #ff9900
integration_connectedUser connects a new Slack or Discord integrationGreen #36a64f
integration_disconnectedIntegration removed or token revokedGray #999999
password_sharedCredential shared via family or group sharingIndigo #4f46e5
password_share_revokedShared credential access revokedRed #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.
import { sendNotification } from "@/lib/notification-dispatcher";

const result = await sendNotification(userId, {
  event: "breach_detected",
  title: "Breach Alert: GitHub",
  message: "Your GitHub credentials were found in a data breach.",
  metadata: { service: "github.com" }
});
// result: { slack: 1, discord: 1, errors: [] }

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 attachment color 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 the access_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 the IconNotification component. Eight preset types cover the full lifecycle of a password reset:
interface InAppNotification {
  id: string
  type: InAppNotificationType
  title: string
  description: string
  color: "success" | "error" | "warning" | "brand" | "default"
  actionable: boolean
  confirmLabel?: string
  dismissLabel?: string
  metadata?: Record<string, string>
  duration?: number   // Auto-dismiss ms; Infinity for actionable toasts
}

Preset Reference

TypeTitleColorActionableButtons
otp_detectedVerification Code DetectedbrandYes”Yes, set password” / “Skip”
trust_deviceTrust This Device?warningYes”Trust” / “Skip”
reset_successPassword Reset CompletesuccessNo— (6s auto-dismiss)
reset_failedReset FailederrorNo— (8s auto-dismiss)
captcha_neededCAPTCHA Needs HelpwarningYes”Open Browser” / “Dismiss”
vault_updatedVault UpdatedsuccessNo— (5s auto-dismiss)
breach_detectedBreach AlerterrorYes”Reset Now” / “Dismiss”
needs_manualAction NeededwarningYes”View Details” / “Dismiss”

Building Notifications with Overrides

The buildNotification function merges preset defaults with caller-provided overrides. It also interpolates {service} placeholders using the metadata.serviceName value.
import { buildNotification } from "@/lib/notifications/notification-presets";

const notification = buildNotification("breach_detected", {
  title: "Breach Alert: {service}",
  description: "Your {service} credentials were found in a recent data breach.",
  metadata: { serviceName: "GitHub" }
});
// => title: "Breach Alert: GitHub"
// => description: "Your GitHub credentials were found in a recent data breach."

Integration Setup

Slack

  1. User creates a Slack app with an incoming webhook.
  2. The webhook URL is stored in the refresh_token field of the integration record.
  3. The dispatcher queries for integration_id = "slack" and connected = true.

Discord

  1. User creates a webhook in their Discord server settings.
  2. The webhook URL is stored as { webhook_url: "..." } JSON in the access_token field.
  3. The dispatcher queries for integration_id = "discord" and connected = true.
Webhook URLs are stored alongside integration records in Supabase. Row-level security policies ensure that users can only access their own integration credentials.

Error Handling

The dispatcher catches errors per-integration and continues delivering to remaining channels. Failed deliveries are returned in the errors array:
ErrorCauseResolution
No Slack webhook URL foundMissing webhook in integration recordRe-connect Slack
Slack webhook failed: 404Webhook deleted in SlackCreate new webhook
No Discord webhook URL foundMalformed access_token JSONRe-connect Discord
Discord webhook failed: 401Webhook token expired or revokedCreate new webhook