# 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
- Add a new
name:secret(or new name) toPUBLISHER_API_KEYS. - Deploy the secret; point the publisher at the new secret.
- 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).