.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.3):
Get a sandbox DID and signing key
Rotation needs an identity that signs. Live
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.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.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
201 — v1 of the credential pair:Rotate — re-mint the same connect_url
Now the leak. To rotate, call the same mint endpoint with the same Live
The leaked v1 secret is now dead. Any inbound delivery signed with it fails the HMAC check
fail-closed; only v2 verifies.
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
201 — v2, the rotated pair (compare every field to v1 above):| Field | v1 | v2 | Rotated? |
|---|---|---|---|
connect_secret | 6NtItJOQ… | pnVMLkBC… | ✅ new |
endpoint_id_label | PxEUb-CD… | oiFMwdVW… | ✅ new |
endpoint_id | 5b4ed680… | 5b4ed680… | ❌ stable |
Fields & errors — the rotate call
Fields & errors — the rotate call
| Field | Type | Notes |
|---|---|---|
connect_url | string | Absolute base URL of your POST /api/ocss/connect receiver. Re-minting the same value rotates; a different value registers a distinct endpoint. HTTPS required outside the sandbox. |
capabilities | string[] | Optional declared capability slugs. You may keep them identical across a rotate. |
| Status | When |
|---|---|
201 | Rotated. New connect_secret + endpoint_id_label; same endpoint_id. |
401 | Signature invalid or wrong kid — sign with the key_id from self-register. |
404 | The signed caller DID does not match {did} in the path (no existence leak). Self-scope only. |
Deploy the new secret
Rotation is only complete once your gatekeeper is running on v2. Swap the two Because your
PHOSRA_* values
in your secret manager and restart — nothing else in the
@phosra/gatekeeper env contract changes: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
| # | Step | Call | Live result |
|---|---|---|---|
| 1 | Get DID + key | POST /advisors/self-register | did:ocss:dx-rotate-dab69f, kid 2026-07, provisional |
| 2 | Mint (v1) | POST /platforms/{did}/endpoints | secret 6NtItJOQ…, endpoint 5b4ed680… |
| 3 | Rotate (v2) | POST /platforms/{did}/endpoints (same connect_url) | secret pnVMLkBC…, same endpoint 5b4ed680… |
| 4 | Deploy v2 | swap PHOSRA_* env | old secret dead, no enforcement gap |
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.