.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. 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.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
201 — v1 of the credential pair:3
Rotate — re-mint the same connect_url
Now the leak. To rotate, call the same mint endpoint with the same Live
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):The leaked v1 secret is now dead. Any inbound delivery signed with it fails the HMAC check
fail-closed; only v2 verifies.
Fields & errors — the rotate call
Fields & errors — the rotate call
4
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
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.