> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Configure notification channels

> Per-category channel toggles, the poll safety net behind the notifications page, and where per-user push subscriptions differ from org webhook sinks.

# Configure notification channels

The [notifications feed guide](/guides/notifications-feed) covers rendering and filtering on `/notifications`. This page covers **where you tune delivery** — per-category channel toggles on **Settings → Notifications**, the per-user digest on **Settings → Notifications → Digest**, and org-level webhook sinks under **Developer → Webhooks**.

## How the two freshness mechanisms interact

The notifications page combines a **live-push stream** (server-sent events on `GET /api/v1/notifications/stream`) with a **30-second poll safety net**. SSE is the primary — a new row lands without a refresh and read state syncs across tabs. The poll exists because a stream frame can be missed: a laptop resumes from sleep between heartbeats, or an inbox row arrives that the stream didn't carry. In those cases the poll self-corrects within 30 seconds, so the feed converges even when the live channel hiccups.

This explains the "sporadic stale content" an operator sometimes sees: **stale for up to \~30 seconds is the fallback working, not a bug** — the poll was re-arming after a dropped stream. The bell badge polls on the same cadence, so it can never get stuck on a stale number either. If rows never converge, that is a real failure — check the stream's connection budget (50 per tenant, enforced cluster-wide, browser UA + trusted Origin required) before filing.

<Note>
  The optional **webhook consumer** path is unaffected by either mechanism — webhooks are server-to-server POSTs to your endpoint regardless of your notifications-page freshness. See [Webhook events](/webhooks/events) and the [webhook consumer guide](/guides/webhook-consumer).
</Note>

## Per-user push subscriptions vs org webhook sinks

These live in different settings surfaces deliberately:

* **Settings → Notifications** is **per user** — each member of the org tunes their own category/channel matrix (`billing`, `security`, `messaging`, `campaigns`, `agents`, `system`) across email, push, and webhook, plus a frequency (`immediate` / `daily` / `weekly`).
* **Developer → Webhooks** is an **org-level sink** — it fans out developer event webhooks to your endpoint, not to a user's notification bell.
* The digest-opt-in API (`GET /api/v1/notifications/digest/settings`) is the tenant-owned control; the dashboard surface above maps to it.

## Per-category channel matrix

Each member sets three toggles per category, and the defaults are on until you change them. Frequencies are `immediate` (default), `daily`, or `weekly`:

| Category  | Email              | Push               | Webhook            | Frequency           |
| --------- | ------------------ | ------------------ | ------------------ | ------------------- |
| billing   | on                 | on                 | on                 | immediate (default) |
| security  | exempt — always on | exempt — always on | exempt — always on | immediate (forced)  |
| messaging | on                 | on                 | on                 | immediate (default) |
| campaigns | on                 | on                 | on                 | immediate (default) |
| agents    | on                 | on                 | on                 | immediate (default) |
| system    | on                 | on                 | on                 | immediate (default) |

Worked example — to route campaign completion emails into one daily email instead of one per event: open **Settings → Notifications**, find the **campaigns** row, and set **Frequency** to `daily`. New campaign events queue on the digest worker and land as one aggregated email instead of N separate ones. Categories other than `campaigns` are unaffected. Security categories ignore whatever you pick here; see the next section.

## Security-critical categories are always on

The following event kinds bypass the matrix entirely — they mint a row and deliver regardless of what the preferences say, so a hijacked session can't silence the takeover signal:

* Sign-in from a new IP
* Sign-in from a new device
* Two-factor disabled or a 2FA method removed
* Password changed
* Email changed (primary or recovery)
* MFA backup codes regenerated
* API key created or revoked
* Member role changed or member removed
* Unusual billing activity / billing anomaly
* Account fraud alert (toll-fraud signal on your own traffic)

Example of a mute being ignored: an attacker who has taken over a session opens **Settings → Notifications** and switches every category off. When they then create an API key and revoke the old one, both events still mint a bell row and dispatch the email to every admin — the attacker succeeded at copying the key out but did not succeed at hiding it.

## Digest API worked sample

The digest is the org-level opt-in for a daily or weekly aggregate email. Fetch the current org settings:

```bash theme={null}
curl https://orbit.devotel.io/api/v1/notifications/digest/settings \
  -H "Authorization: Bearer <api-key>"
```

Response (defaults shown — `enabled` off, `weekly`, every category):

```json theme={null}
{
  "data": {
    "organization_id": "org_abc123",
    "enabled": false,
    "frequency": "weekly",
    "categories": []
  }
}
```

An empty `categories` array means all six categories are included. Turn the digest on with a daily cadence restricted to campaigns and billing:

```bash theme={null}
curl -X PUT https://orbit.devotel.io/api/v1/notifications/digest/settings \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "frequency": "daily", "categories": ["campaigns", "billing"]}'
```

The route writes an audit-log row recording before/after. Both `GET` and `PUT` require an authenticated session; `PUT` is restricted to `owner` and `admin`.

## Freshness debugging — decision table

Use this when the notifications page looks stale:

| Symptom                       | Meaning                                            | Action                                                 |
| ----------------------------- | -------------------------------------------------- | ------------------------------------------------------ |
| Rows converge in ≤30 s        | SSE dropped, poll fallback covered it              | Nothing — the fallback is working as designed          |
| Rows never converge           | Live stream is broken                              | Check the connection budget (below)                    |
| Badge stale, list fresh       | Badge poll lags on the same 30 s cadence           | Wait one cycle; if it sticks, hard-refresh and recheck |
| Webhook arrives, bell doesn't | Server-to-server POSTs are independent of SSE/poll | This points to the SSE path, not delivery              |

Connection budget checks, in order:

1. The stream caps at **50 concurrent connections per tenant**, enforced cluster-wide — a browser with many open tabs on the same org can exhaust it.
2. The stream requires a **browser user-agent** and a **trusted origin**. API clients and non-browser proxies are rejected.
3. If neither is the cause, file it with a timestamp and the affected org id.

## Operator walkthrough

1. **Settings → Notifications** — tune your own per-category email/push toggles and pick a frequency per category. This affects only you, not your teammates.
2. **Settings → Notifications → Digest** — flip the org digest on/off, choose `daily` or `weekly`, and narrow the categories. This affects the org, and only owner/admin roles can change it.
3. **Developer → Webhooks** — register an endpoint for developer event fan-out. This is unrelated to the bell and unaffected by any setting above; the divergence boundary is that per-user push runs through your personal preference matrix whereas the org webhook sink fires regardless of any member's opt-out.

## Not covered today

The notifications section in **Settings** is dashboard-only — there is no public API for the per-category channel matrix itself (the digest API covers org-level outlet selection). The webhook guide remains separate; this page only covers where the operator-facing toggles live.
