# Publishing
Publishing means creating a notification for a user. There are two safe paths — pick the right one.
flowchart TD
Q{"Who is the recipient?"}
Q -->|"The signed-in user"| Self["publishSelf / notification.success"]
Q -->|"Someone else"| Pub["publish with publisher token"]
Self --> API1["POST /v1/notifications/self"]
Pub --> API2["POST /v1/notifications"]# Path A — Notify the current user (browser OK)
Use when the action happens in the SPA and the inbox item is for me:
await notification.success('Export ready', 'Your file is ready.', '/exports/1');
// or:
await notificationsClient.publishSelf({
title: 'Export ready',
body: 'Your file is ready.',
severity: 'success',
actionUrl: '/exports/1',
});
- Any valid user token works
- Recipient is forced to token
sub— cannot target another user - Convenience helpers add a unique
dedupeKeyper call
# Path B — Notify another user (backend / M2M)
Use for jobs, webhooks, and server workflows (“Assess finished a review for surgeon X”):
await notificationsClient.publish({
userGuid: recipientSub, // Cognito sub of the recipient
title: 'Review complete',
body: 'Your assessment feedback is ready.',
severity: 'success',
actionUrl: `/video/${videoId}/feedback`,
dedupeKey: `review_case:${caseId}`,
metadata: { caseId, videoId },
});
Requirements:
- Token must be a publisher (scope in
PUBLISHER_SCOPES, typicallynotifications/publish, or group inPUBLISHER_GROUPS) userGuidmust be the recipient’s Cognitosub(same value they authenticate with)
# Getting an M2M token
PowerShell (Windows):
$ClientId = "…" # Cognito app client → Client ID
$ClientSecret = "…" # Cognito app client → Client secret
$CognitoDomain = "https://<prefix>.auth.<region>.amazoncognito.com" # App integration → Domain
curl.exe -X POST "$CognitoDomain/oauth2/token" `
-H "Content-Type: application/x-www-form-urlencoded" `
-u "${ClientId}:${ClientSecret}" `
-d "grant_type=client_credentials&scope=notifications/publish"
Use curl.exe (PowerShell’s curl is not curl). Bash/macOS: see cognito-setup.md.
Cache until expires_in. Don’t fetch a token per notification.
Wire it into the client:
const client = new NotificationsClient({
baseUrl: 'https://notifications.totumcloud.com',
getAccessToken: () => getCachedM2MToken(),
});
# Always set dedupeKey for jobs
dedupeKey is how you make publishing idempotent.
Without dedupeKey |
With dedupeKey |
|---|---|
| Two retries can create two rows | Same (user, source, key) → one row |
| Client will not auto-retry publish | Client will retry safely on 5xx/network |
| Fine for one-off self-notifies with helper keys | Essential for background jobs |
Good keys look like domain facts:
review_case:8d2e4c…
invite:org:42:user:abc
export:job:991
assessment_assigned:entity-77
Bad keys: random values that change every retry, empty strings, “notification”.
# Response semantics
| HTTP | Body | Meaning |
|---|---|---|
201 |
{ notification, duplicate: false } |
Created |
200 |
{ notification, duplicate: true } |
Key already existed — original row returned |
Treat both as success in job runners.
# Plain HTTP (no client)
curl -X POST https://notifications.totumcloud.com/v1/notifications \
-H "Authorization: Bearer $M2M_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userGuid": "cognito-sub-here",
"title": "Review complete",
"body": "Your feedback is ready.",
"severity": "success",
"actionUrl": "/video/abc/feedback",
"dedupeKey": "review_case:8d2e"
}'
# Field reference (publish body)
| Field | Required | Notes |
|---|---|---|
userGuid |
yes (cross-user only) | Recipient Cognito sub. Omitted on /self |
title |
yes | Max 200 chars |
body |
yes | Max 2000 chars |
severity |
no | Default info |
actionUrl |
no | App-relative or absolute deep link |
dedupeKey |
strongly recommended | Max 256 chars |
metadata |
no | Opaque JSON, ≤ 8 KB |
source is never client-supplied — derived from the token.
Unknown fields are rejected (400) on the V2 API. Put extra context in metadata.
# Security reminders
- Never ship M2M client secrets to the browser
- Don’t grant every user the publish scope “so the SPA can notify anyone”
- Prefer Path A for browser self-notifies; Path B for trusted servers
Next: V1 compatibility if you still have /api/Notification/* callers.