# Publisher API keys

The easiest way to let a backend publish notifications — no Cognito resource server, no M2M app client, no token fetch.

Prefer Cognito machine-to-machine (M2M) when you already have it wired (rotation, short-lived tokens, scopes). Use API keys when you want to publish today with one Worker secret.

Inbox read/mark/delete still needs a normal user Cognito access token. API keys are publish-only.

# Not the same as V1’s shared key

V1’s Video Library pattern was often one anonymous secret (REVIEW_TOOL_API_KEY / INGEST_API_KEY) copied between projects, sometimes with a permissive database insert. That is what “shared API keys were bad” referred to.

V2 publisher keys are different:

V1 shared key V2 PUBLISHER_API_KEYS
Identity Anonymous / trust Named (assess:…source=assess)
Gate Often edge function + open insert Only the Worker; publish-only
Who can have one One blob for everyone One name per producer (easy to rotate/revoke)

# Setup (about two minutes)

Full local walkthrough (API keys first, then optional M2M): local-development.md. Deployed Worker: setup.md.

# 1. Put keys on the Worker

Format: comma-separated name:secret pairs. The name becomes notification source (for dedupe and audit).

Generate a long random secret (password manager, or openssl rand -hex 32). Longer is fine — use something high-entropy; do not reuse the same secret across environments. The Worker rejects obviously short placeholders so misconfiguration fails fast.

# .dev.vars (local) or Worker secret (staging/production)
PUBLISHER_API_KEYS=assess:<long-random-secret>

Multiple publishers:

PUBLISHER_API_KEYS=assess:<long-random-secret>,vl-jobs:<another-long-random-secret>

Preferred (dashboard): Worker → Settings → Variables and Secrets → Add → type Secret, name PUBLISHER_API_KEYS, paste the value → Deploy.

Backup (CLI):

wrangler secret put PUBLISHER_API_KEYS --env production
# paste: assess:<long-random-secret>

You do not need PUBLISHER_SCOPES for API-key publishers. Keep Cognito AUTH_ISSUER — users still need it for inboxes.

# 2. Call the API

curl.exe -X POST "http://127.0.0.1:8787/v2/notifications" `
  -H "Content-Type: application/json" `
  -H "X-Api-Key: <long-random-secret>" `
  -d "{\"userGuid\":\"<recipient-sub>\",\"title\":\"Hello\",\"body\":\"From API key\",\"dedupeKey\":\"demo:1\"}"

Or with the npm client (backend only — never ship the key to a browser):

import { NotificationsClient } from '@inovus-medical/notifications';

const client = new NotificationsClient({
  baseUrl: 'https://notifications.totumcloud.com',
  apiKey: process.env.NOTIFICATIONS_API_KEY!,
});

await client.publish({
  userGuid: recipientSub,
  title: 'Review complete',
  body: 'Your feedback is ready.',
  dedupeKey: `review_case:${caseId}`,
});

source on the created notification is the key name (e.g. assess).


# Rules

Rule Detail
Header X-Api-Key: <secret> (not the name)
Publish POST /v2/notifications and V1 Create / CreatePayload
Inbox Forbidden (403) — use a user JWT
Both headers Sending Bearer and X-Api-Key → 400
Browser Never put publisher API keys in SPA / mobile clients

# Rotation

  1. Add a new name:secret (or new name) to PUBLISHER_API_KEYS.
  2. Deploy the secret; point the publisher at the new secret.
  3. Remove the old pair from the env and redeploy.

One name per producer keeps audit and dedupe clear.


# Cognito M2M vs API keys

API keys Cognito M2M
Setup One env var Resource server + confidential app client + token endpoint
Credential lifetime Long-lived secret Short-lived access token
Best for Quick bootstrap, simple jobs Production services that already use Cognito

Both can be enabled at once. See Cognito explained and Cognito setup for M2M.

Cross-user publish is also subject to rate limiting (600 requests / 60s per key name).