Triage the Notification Center
The Notification Center is the dashboard’s persistent alert feed — the full list behind the bell icon. Where the bell dropdown gives you a quick glance at what’s new, this page is where you triage, batch-mark-read, and review history. Its own subtitle says what it holds: persistent platform alerts that need your attention. Open it from /notifications (bell icon, top right).What a notification is
A notification is a durable, row-scoped platform event that surfaced somewhere in your org and needs a human view. It carries:- a title + body — what happened, in plain terms
- a kind — which event class it belongs to (campaign, payment, security, inbox, …)
- a severity —
info,warning, orerror - an optional action URL — a deep link to the surface that resolves it
- a read/unread state and an optional expiry
What raises a notification
Notification kinds are a closed enum on the API, and the dashboard’s filter dropdown mirrors that enum. The major groupings: Platform alertscampaign_completed/campaign_failed— a campaign finished (or finished with errors)payment_failed/top_up_succeeded/credit_low/wallet_emptied/number_renewal_failed/unusual_billing_activity— billing state changes. Anumber_renewal_failedrow means a held number’s renewal charge hit an empty wallet; the recovery path (top up, auto-release, reclaim) is in the INSUFFICIENT_BALANCE troubleshooting tree.dlq_alert/webhook_endpoint_disabled/webhook_endpoint_decryption_failed— integration failuresport_status_changed/telnyx_*(regulatory, number order, port request) /number_compliance_requested— number lifecyclekyc_decision/sender_id_review/compliance_incident— compliance decisionsaccount_fraud_alert— account-level fraud / traffic anomalyemail_dns_regression— deliverability regressionsystem_alert— generic platform alert
new_signin_from_new_ip/new_signin_from_new_device/password_changed/email_changed/recovery_email_changedtwo_factor_disabled/two_factor_method_removed/mfa_backup_codes_regeneratedapi_key_created/api_key_revokedorg_member_role_changed/org_member_removed
Security kinds are always shown regardless of your notification preferences — suppressible preferences would let a hijacked session mute the takeover signal.
agent_handoff_pending— AI agent escalated to a humanagent_kpi_alert_fired/voice_queue_sla_warning/voice_queue_alert_rule_fired/messaging_deliverability_alert_fired/acd_disposition_required— operational thresholds breachedinbox_new_message/inbox_conversation_assigned/inbox_new_conversation/inbox_mention/inbox_sla_breach/inbox_follower_update/inbox_ticket_follower_update/team_chat_message/csat_low/whatsapp_template_status_changed— inbox + messaging state changes
Delivery channels
Every notification lands in-app — the bell row, the live stream, and the Notification Center list. Outbound delivery is layered on top:- In-app — persisted row + live SSE feed. This is the only delivery for a few high-frequency, low-stakes kinds (for example
team_chat_message), where per-event email would be spam. - Email digest — for outbound-eligible kinds, a per-user digest (daily or weekly) rolls up unread notifications in opted-in categories:
billing,security,messaging,campaigns,agents,system. Configure it under Settings → notification digest, or via the API below. - Category-level preferences — you can mute a whole category for outbound delivery per user (Settings → notifications). Muted categories still mint the in-app bell row; security-critical kinds ignore the mute entirely.
Digest API
Admins manage the digest from the API; member opt-ins are per user. Read the org’s settings:PUT /api/v1/notifications/digest/settings accepts enabled (boolean), frequency ("daily" or "weekly"), and categories (an array of billing, security, messaging, campaigns, agents, system). An empty categories array means every category is emailed.
List recipients:
POST /api/v1/notifications/digest/preview (?hours= bounds the lookback window, max 168).
Read, dismiss, retention
- Read/unread — a
PUT /notifications/:id/readcall marks a single row read;POST /notifications/read-allclears the badge. The dashboard streams delivery so read state syncs across open browser tabs. - Dismiss — user-scoped notifications can be dismissed; the row hides immediately and the delete commits after a short undo window (~5s in the UI).
- Retention — rows carrying an
expires_attimestamp are automatically filtered out of every list and unread-count response after that moment passes, so a stale “campaign completed” never lingers past its useful life. Rows without an expiry persist until you dismiss them.
API walkthrough: list, badge, mark-read, dismiss
All responses share the standard envelope:data plus a meta block with request_id and timestamp.
Check the badge:
id, user_id, org_id, kind, severity, title, body, action_url, read_at, created_at, expires_at, source_pillar, and category — no other fields. Filters on GET /notifications: unread_only, kind, severity, source_pillar (alias pillar), category, cursor, limit. GET /notifications/global adds since / until ISO timestamps for time-boxed feeds.
A malformed filter on a list read never returns 422: the API ignores it and loads the list unfiltered, so a stale bundle or an off-range
limit still renders the feed.Live tail over SSE
GET /notifications/stream is a text/event-stream endpoint for your own queueing worker, not just the dashboard. Browsers authenticate it with a short-lived one-time token because EventSource cannot set headers:
POST /notifications/sse-token→{ "data": { "ot": "sset_…", "expires_in": 90 } }- Pass
?ot=<token>(preferred) or?token=<JWT|API-key>as a query param on the stream URL. Adv_-prefixed token is treated as an API key; anything else as a bearer token.
fetch (also the pattern the @devotel/sdk-node client wraps; the SDK’s notifications.list / markRead / markAllRead / archive methods back the same routes):
:heartbeat comments plus an optional connected frame ({agent.client} is the only event type you’ll usually need the envelope for). The dashboard-facing freshness model — how that stream and a 30-second poll safety net converge — is covered in Configure notification channels. Connection budget: 50 per tenant, enforced cluster-wide; a browser UA + trusted Origin header is required.
Sample framed payload on the stream:
Security-row shape
Security-critical events (account takeover, 2FA disabled, password/email change, API-key mint/revoke, role change, billing anomaly, decryption-failure, fraud alert) always mint a row and always bypass user preference muting — a hijacked session can’t silence them:Notifications vs webhooks
Notifications are for a human operator; webhooks are for your code.- A notification is a durable UI row an operator triages from the dashboard. Read, dismiss, filter — no endpoints to expose.
- A webhook is a server-to-server POST to an endpoint you control. You subscribe to webhook events and process them automatically.
Operator runbook tips
The notification center and on-call alerting serve different altitude:- Notification Center = state an entire org sees; any member with dashboard access can triage it.
- On-Call alerting = an engineering-to-action escalation loop (rotation → escalation policy → page via Messages) for time-critical issues.
- A dashboard notification lands (e.g.
dlq_alert,wallet_emptied,webhook_endpoint_disabled). - Your on-call rotation consumes that queue if it needs a human now — page the current on-call via On-Call, with dashboard access to review the row before acting.
- If the alert is genuinely routine, the center’s filters + category digests keep on-call pages from flooding the rotation with noise.