Skip to main content
Before a provider can complete the connect ceremony and start delivering endpoint_id_labels to your app, your platform registers one endpoint with the census. Registration:
  1. declares your webhook URL — the base URL your POST /api/ocss/connect receiver lives under, and
  2. returns two secrets exactly once — the endpoint_id_label (your §9.3(a) registration name) and the connect_secret (the HMAC key that authenticates inbound connect-leg deliveries).
This is the OCSS Trust Framework §3.5 out-of-band establishment step made a real operation — the public endpoint below is its normative surface; the @phosra/gatekeeper SDK wraps it end to end.
One endpoint, self-scoped. You register your own platform DID’s endpoint — the signed request’s caller DID must equal the {did} in the path. A mismatch answers the same 404 an unknown platform answers (no existence leak). Re-minting the same (did, connect_url) pair rotates both credentials — that is the EXT-04 §3.5 re-issue path.

Prerequisites

Read your key_id back — never construct it. Every DID on the Trust List — seeded roster or self-registered — publishes a plain did:ocss:<slug>#<YYYY-MM> kid, and the month is not guessable: did:ocss:snaptr publishes #2026-07, did:ocss:loopline publishes #2026-06. There is no slug-YYYY-MM “bespoke” form. For a self-registered sandbox DID the census binds your key under the UTC month at registration and returns your key_id directly in the self-register 200 body — use it verbatim. Signing with any kid that is not the one your entry publishes fails 401 (kid-not-in-entry). Read it back from your entry — see Onboarding → Read back your bound kid — and use it verbatim everywhere PHOSRA_PLATFORM_KEY_ID / keyID appears.

Step 1 — Mint your endpoint — POST /api/v1/platforms/{did}/endpoints

The request is RFC-9421 signed with your DID’s Ed25519 key (the census verifies the signature against your live Trust-List entry — provisional-tier self-registrations included). There is no phosra_ API key on this path; the census identifies you by signature, not by a bearer token. Request body: Because the request must be signed, mint through the SDK rather than raw curl. Using signRequest from @openchildsafety/ocss:
201 response — the two secrets are here and nowhere else (field order + value format live-verified against the sandbox census):
endpoint_id_label and connect_secret are each an unprefixed 43-character base64url string (a raw 32-byte secret, -_ alphabet, no eplbl_/cs_ prefix) — the two example values above are clearly-fake placeholders in that exact format.
endpoint_id_label and connect_secret are returned only once. Per §9.3, the census stores only their SHA-256 digests (migration 191_ocss_platform_endpoints) — the cleartexts live in this 201 body and nowhere else. They are never logged, receipted, or exported. Persist both immediately (a secret manager, not source). Lose them and you re-mint, which rotates both and invalidates the old pair.

What each returned value is for


Step 2 — Declare your webhook receiver

The connect_url you registered is the base your POST /api/ocss/connect receiver lives under. When a provider completes the connect ceremony, Phosra delivers the endpoint_id_label server-to-server to that URL. Stand up the receiver and hand it to @phosra/gatekeeper’s handleConnect — see Platform Quickstart → Step 1:
The sender appends /api/ocss/connect to your registered base, so register the base (e.g. https://snaptr.example.com/webhooks/ocss-connect) — the SDK and the census sender agree on this path convention.

Step 3 — Verify inbound deliveries with the connect-secret

Phosra signs every connect-leg delivery with an X-Phosra-Signature header so your receiver can prove the request came from Phosra and was not tampered with. Algorithm: X-Phosra-Signature = lowercase_hex( HMAC-SHA256( connect_secret, rawRequestBody ) ). @phosra/gatekeeper’s handleConnect implements the verify side for you. Pass the connect_secret from Step 1 into createGatekeeper and the SDK verifies it for you, fail-closed:
Always configure connectSecret in production. As of @phosra/gatekeeper@0.3.0, handleConnect fails closed (503) when connectSecret is unset — unless you set allowUnauthenticatedConnect: true for a trusted-network deployment. The connect_secret is what binds an inbound delivery to Phosra.

The @phosra/gatekeeper env contract — the six PHOSRA_* vars

A gatekeeper deployment is fully parametrized by six PHOSRA_* environment variables plus one secret signing seed. Everything createGatekeeper needs maps 1:1 onto them: Plus the signing seed — the one genuinely sensitive value, kept out of the six because it is a private key, not configuration:
PHOSRA_TRUST_ROOT_X is a public key pinned out-of-band on purpose — that is what root verification means: you do not fetch the root from the census you are about to verify. The staging census is a Phosra-internal pre-release instance with a different root and is not a partner surface — always use the canonical production sandbox host above.

Conformance

The signed endpoint-mint (Step 1) needs your DID key, so it is exercised by the SDK, not raw curl. What a docs-only stranger — or the nightly docs-conformance CI — can run with no credentials is the reachability + trust-anchor check the whole registration depends on: the canonical census serves a root-signed Trust List whose root matches the PHOSRA_TRUST_ROOT_X pinned above.
A signed endpoint-mint round-trip (mint → receive endpoint_id_label at your webhook → handleConnect 200 → refreshProfileisAllowed) is exercised end-to-end by scripts/sandbox/verify-web-engagement-rule.mjs, which exits 0 only if the full chain survives against the live sandbox census.

Next

  • Platform Quickstart — receive the connection, fetch/verify the profile, enforce locally, confirm
  • Onboarding — get your sandbox did:ocss:<slug> (self-serve, no email)
  • Conformance status — the one canonical sandbox host and every live/preview surface