Run the connect ceremony to your own DID (sandbox)
Sandbox only. Gated on PHOSRA_ENV==sandbox — returns 404 on dev, staging, and
production.
Runs the provider side of the EXT-04 §3.2 connect ceremony against your own platform DID +
webhook, so a cold self-registered platform dev receives a real signed binding with no live
counterparty and no roster edit. It mints a §9.3 binding for a seeded test child scoped to your
platform_did and delivers the signed §3.6 callback to your webhook_url. The call is
RFC 9421-signed as you.
Pick which seeded child with child_ref (optional). Omit it and the binding is minted for
Mia; pass a seeded child ref to bind a different one. This is how you exercise the
multi-profile model — one call per child yields a distinctendpoint_id_label per child,
which is exactly what a service with several kids' profiles must map and enforce separately (see
Bind a connection to the right child):
Seeded child
child_ref
Mia (default)
child:a11ce0fa-0000-4000-8000-0000000000a1
Leo
child:a11ce0fa-0000-4000-8000-0000000000a2
Ava
child:a11ce0fa-0000-4000-8000-0000000000a3
The binding is minted and profile_url is observable even if the webhook delivery leg
fails — delivered=false with a note explaining why, but you can still poll the profile.
Worked example
ts
import { signRequest } from "@openchildsafety/ocss"const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"const seed = new Uint8Array(Buffer.from(process.env.OCSS_SANDBOX_TEST_SEED_B64URL!, "base64url"))const keyID = process.env.OCSS_SANDBOX_KEY_ID!const targetURI = BASE + "/sandbox/test-connect"const body = { platform_did: process.env.OCSS_SANDBOX_DID!, webhook_url: "https://your-gatekeeper.example.com", // POST /api/ocss/connect receiver base connect_secret: process.env.OCSS_SANDBOX_CONNECT_SECRET!, child_ref: "child:a11ce0fa-0000-4000-8000-0000000000a2", // OPTIONAL — bind Leo; omit → Mia (see table above)}const bodyText = JSON.stringify(body)const headers = signRequest({ method: "POST", targetURI, body: new TextEncoder().encode(bodyText), keyID, seed, created: Math.floor(Date.now() / 1000) })headers["Content-Type"] = "application/json"const res = await fetch(targetURI, { method: "POST", headers, body: bodyText })console.log(res.status, await res.json())
Example 200 sandbox response (example.com returns 405 to the callback, so
delivered=false; the binding is minted regardless):
json
{ "binding_id": "145c2779-1759-4378-abf3-c49a49408ede", "resolver_did": "did:ocss:loopline", "endpoint_id_label": "REDACTED_SANDBOX_ENDPOINT_ID_LABEL", "profile_url": "/api/v1/enforcement-profiles/REDACTED_SANDBOX_ENDPOINT_ID_LABEL", "connect_receiver": "https://example.com/api/ocss/connect", "delivered": false, "http_status": 405, "state": "sandbox-test-connect:145c2779-1759-4378-abf3-c49a49408ede", "note": "callback delivery did not receive a 2xx — check webhook_url reachability and that connect_secret matches your gk.config; the binding is minted and profile_url is observable regardless"}
Poll profile_url with your platform key (fetch profile)
to see the router-signed profile land, and rotate
with the returned binding_id.