# App integration
How product apps wire into Notifications V2: browser inbox, server-side cross-user publish (API key stays off the SPA), the npm client, and rate limits.
flowchart LR SPA["SPA"] -->|"user JWT"| NpmClient["@inovus-medical/notifications"] NpmClient -->|"inbox / self"| API["notifications Worker"] SPA -->|"app session only"| BFF["Your edge / API route"] BFF -->|"X-Api-Key from env"| API
# 1. Browser path (user JWT)
Install the published client:
npm install @inovus-medical/notifications
import { NotificationsClient, createNotificationService } from '@inovus-medical/notifications';
const client = new NotificationsClient({
baseUrl: 'https://notifications.totumcloud.com',
getAccessToken: () => auth.getValidAccessToken(),
});
export const notification = createNotificationService(client);
| Allowed with user JWT | Not allowed |
|---|---|
| List / mark read / delete own inbox | Cross-user publish({ userGuid }) (403) |
publishSelf / notification.success(...) to self |
Shipping PUBLISHER_API_KEYS or M2M secrets in the browser |
Full narrative: Publishing — Can an app use a user’s JWT?.
Agent brief: AGENT.md.
# 2. Cross-user path (API key or M2M — server only)
When Assess / a job / a webhook must notify another user:
- Hold a publisher credential in server env (
NOTIFICATIONS_API_KEYor Cognito M2M client secret). - Call the Worker from that server (or from a small BFF in front of the SPA).
- Always set a meaningful
dedupeKey.
SPA must not call the Worker with an API key. If the UI triggers “notify surgeon X”, the SPA calls your backend; your backend calls Notifications.
| Credential | Where it lives |
|---|---|
X-Api-Key from PUBLISHER_API_KEYS |
Worker secret + your BFF/job env — api-keys.md |
Cognito M2M + notifications/publish |
Your job’s secrets — cognito-setup.md |
# 3. BFF / edge proxy contract
Minimal pattern (see runnable samples):
| Item | Detail |
|---|---|
| Route | e.g. POST /api/notifications/publish on your app |
| Auth | Verify the caller with your session / Cognito user JWT (same product pool). Reject anonymous. |
| Body | Same shape as Worker publish: userGuid, title, body, optional severity, actionUrl, dedupeKey, metadata |
| Outbound | NotificationsClient({ baseUrl, apiKey }) or fetch + X-Api-Key to POST /v2/notifications |
| Never | Accept a client-supplied API key; return the key in responses |
Samples:
- examples/publish-proxy-worker — Cloudflare Worker
- examples/publish-proxy-next — Next.js App Router route
Long-running jobs can skip the BFF and call the Worker directly with the same env-held key.
# 4. Rate limits (abuse mitigation)
Full detail: Rate limiting.
Short version: after auth, inbox/self is capped at 300 requests / 60s per user; cross-user publish at 600 / 60s per publisher. Over the limit → 429 with Retry-After: 60. The npm client retries 429 when retries are allowed.
# 5. Checklist
- [ ] SPA uses npm client + user token for inbox / self only
- [ ] Cross-user publish goes through BFF or backend job with env-held key
- [ ]
dedupeKeyset on every cross-user publish - [ ] Client
subscribe()stopped on logout - [ ] Handle
429(client retries by default)