# 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 dedupeKey per 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, typically notifications/publish, or group in PUBLISHER_GROUPS)
  • userGuid must be the recipient’s Cognito sub (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.