Skip to main content
A secret leaked — it landed in a log, a screenshot, a committed .env. The fix is not “delete and start over”; it is rotate: mint a new secret, invalidate the old one, and keep the integration running. In Phosra a platform’s inbound credential pair — the connect_secret (the HMAC key that authenticates deliveries to your webhook) and the endpoint_id_label (your registration name) — is rotated by re-minting your endpoint. Re-minting the same (did, connect_url) pair issues a fresh pair and retires the old one, while your stable endpoint_id never changes. Every request and response below is verbatim live output, captured against https://phosra-api-sandbox-production.up.railway.app. The self-register step is plain curl; the mint and rotate are RFC-9421 signed, so they run through the @openchildsafety/ocss SDK rather than raw curl (there is no phosra_ API key on this path — the census identifies you by signature).
Sandbox-first. Self-registration is sandbox-only (SANDBOX_MODE=true) — nothing here touches a real Trust List. In production your DID arrives via accreditation instead of self-register, but the mint/rotate call is identical: same endpoint, same signed shape, same rotation semantics.

Before you start

Install the protocol SDK — it is published to npm (@openchildsafety/ocss, current 0.1.5):
1

Get a sandbox DID and signing key

Rotation needs an identity that signs. POST /api/v1/advisors/self-register puts a fresh did:ocss:<slug> on the sandbox Trust List, bound to a public key you generate. Keep the seed private; publish only the public half.
Live 200 — note key_id: the census binds your key under a bare kid equal to the current UTC month. That is the keyID you sign with everywhere below.
Sign with exactly the key_id the census returns. A self-registered DID binds under did:ocss:<slug>#<YYYY-MM>; signing with any other kid fails 401 (kid-not-in-entry). Do not invent a bespoke kid — read it back from the registration response.
2

Mint your endpoint — the credential you will later rotate

POST /api/v1/platforms/{did}/endpoints mints your endpoint and returns the two secrets exactly once. The request is RFC-9421 signed with the key from step 1 — signRequest covers the request line and a Content-Digest of the body.
TypeScript
Live 201v1 of the credential pair:
connect_secret and endpoint_id_label are each an unprefixed 43-character base64url secret returned only in this body — the census stores only their SHA-256 digests. Persist both to a secret manager immediately. If either leaks, you rotate (next step). endpoint_id is the stable, loggable UUID — it is not a secret and does not change when you rotate.
3

Rotate — re-mint the same connect_url

Now the leak. To rotate, call the same mint endpoint with the same connect_url. The census recognises the (did, connect_url) pair and re-issues both secrets — this is the OCSS Trust Framework §3.5 re-issue path. Your endpoint_id is preserved; the old connect_secret and endpoint_id_label stop verifying.
TypeScript
Live 201v2, the rotated pair (compare every field to v1 above):
The leaked v1 secret is now dead. Any inbound delivery signed with it fails the HMAC check fail-closed; only v2 verifies.
4

Deploy the new secret

Rotation is only complete once your gatekeeper is running on v2. Swap the two PHOSRA_* values in your secret manager and restart — nothing else in the @phosra/gatekeeper env contract changes:
Because your endpoint_id, DID, and signing key are unchanged, already-established connections keep working — a rotate re-keys the inbound channel without tearing down the family links you already hold. There is no enforcement gap.

The whole flow at a glance

Next steps

Platform registration

The full endpoint-mint contract, the six PHOSRA_* env vars, and the webhook verify side.

Onboarding — get a sandbox DID

Self-register, read back your bound kid, and what provisional tier can and cannot do.

Back off and retry a 429

The other half of resilient auth — a leaked-then-rotated key often surfaces first as a 401.

Error reference

signature_invalid, kid-not-in-entry, and every other auth failure documented in one place.