Skip to main content
This is not where a new platform integration starts. New platform integrations begin at the Platform QuickstartcreatePlatform({ credential, adapter }) plus the /api/phosra/[...phosra] catch-all. The credential-driven facade owns the OAuth surface, delivery, retry, workers, and readiness reporting, and it is what the reference platform integrations run.This page documents the legacy connect-receiver lane: createConnectReceiver from @phosra/gatekeeper/next. It remains supported for existing integrations. It is the right lane when:
  • you already run a createConnectReceiver receiver in production and are not ready to move;
  • you are draining an HMAC connect secret through the HMAC → Signed migration, which is defined in terms of this receiver’s legacyHmacSecret posture; or
  • you hold an Ed25519 sender seed but no PHOSRA_CREDENTIAL yet (issuance is operator-gated), and you are pairing with a writer-plane provider in the sandbox.
Which lane am I on? Look at your Phosra input.
createConnectReceiver (from @phosra/gatekeeper/next, v0.6.0+) collapses the OCSS connect receiver to one route file and one allowlist. It verifies every delivery to the pinned OCSS root — the same root your gatekeeper already pins to fetch enforcement profiles — so the provider’s signature is the authentication. There is no shared HMAC secret to mint, store, sync, or rotate.
The low-level createGatekeeper() and every existing gatekeeper export are unchanged — the enforce/confirm loop (refreshProfileisAllowedconfirm) that pairs with this receiver is documented on Platform Registration. This page covers the connect leg the receiver handles.

The entire receiver

That is the complete /api/ocss/connect route. The provider’s Ed25519 signature, verified to the root, is the auth; the allowlist is the authorization. Revoke a provider by removing its DID from authorize — no key exchange, no rotation.
Adding the route to your app does not make it discoverable by Phosra Link. Complete the platform-registration step in the Phosra developer console (or with the signed registry API) before testing a parent flow:
  1. Publish the OAuth surface (authorize_url, par_url, token_url, profiles_url, scopes, and optional profile-management URL) through the platform connect-metadata operation.
  2. Publish the delivery receiver through the separate platform endpoint-registration operation. Do not put connect_url in the connect-metadata request; the two operations are deliberately separate so editing OAuth copy or URLs cannot silently rotate delivery credentials.
  3. Store the endpoint-registration response in a secret manager immediately. It contains one-time credential fields for compatibility lanes; never print the response, commit it, or paste it into a build log. A signed-only createConnectReceiver does not use the legacy HMAC secret at runtime, but registration still treats every returned credential as sensitive.
The published connect_url must be the exact public POST destination owned by your chosen integration surface. For the low-level receiver on this page that is normally https://your-app.example/api/ocss/connect; a platform using the Phosra Link Next.js facade may instead expose a facade-owned route such as /api/phosra/delivery. Do not guess or copy another platform’s path—use the route exported by the SDK surface you installed. Verify discovery before opening the parent UI:
If OAuth approval succeeds but the parent app fails while saving the connection, check this discovery response first. A missing connect_url means the platform authenticated the parent but never completed delivery registration; retrying the browser flow cannot repair registry configuration.

Config

authorize is not optional and it is the whole trust surface. A signed delivery from any accredited enforcement-agent is authenticated, but only a DID on your authorize list is authorized. Making the field required means you cannot accidentally ship a receiver that trusts every accredited provider on the Trust List.

How a delivery is verified — nothing you write

createConnectReceiver reads the raw request body once and dispatches by shape:
  1. Signed provision delivery (isSignedProvisionDelivery(body)) → verifyProvisionDelivery → create-or-adopt N age-banded profiles, then onBound per profile. This is the create-and-link fan-out from the provider.
  2. Signed connect envelope / { endpoint_id_label, state }handleConnect → bind the single label, then onBound(label, childRef).
  3. Legacy ocss-ext01/provision.v1 HMAC batch → the HMAC lane — only when legacyHmacSecret is set.
For (1) and (2), the SDK inherits audience binding (the delivery must name your did), created-freshness (within createdSkewSec), and the authorize gate from the verified envelope — all before your onBound runs. You verify nothing by hand.
onBound fires post-2xx, per bound label. On the batch path each call carries a ProvisionContext so you know the age band and whether to auto-set an adult PIN; on the single-bind path provision is undefined. A throwing onBound is routed to onError and does not fail the delivery (the binding already landed).

After the connect leg — enforce

Receiving the connection is only the connect leg. The bound endpoint_id_label is your profile-poll target: fetch and verify the signed enforcement profile, decide locally, and confirm — all with the low-level @phosra/gatekeeper:
The full profile → isAllowedconfirm loop (including the two-endpoint_id_label gotcha and the fail-closed rules) is documented on Platform Registration, the low-level createGatekeeper page. (The Golden Platform Quickstart does not use this loop — its createPlatform facade owns apply/observe internally.)

Trust-list liveness

Signed verification role-gates the signer to an active accredited enforcement-agent and verifies to root, so the connect leg now depends on Trust-List availability and accreditation freshness (the 7-day attestation TTL) — a coupling the old HMAC path never had. This is a deliberate trade, not a surprise: cache the last-known-good Trust List so a transient census blip doesn’t reject a legitimate, still-accredited provider. Name it in your ops runbook. See Migration → Liveness. When Phosra issues a production Link credential, it publishes the credential’s active Link public key and the enforcement-agent role atomically. There is no separate manual role grant for the normal onboarding path; a conflicting pre-existing role makes issuance fail closed.

Branding is an assessed conformance item

Two parts, both checked at accreditation: (1) co-brand your OAuth leg — the authorize page the parent hits during the ceremony MUST read Phosra Link · <Platform>, never a bare auth form; (2) provenance — once a profile is bound, surface a persistent “Managed via Phosra” label on the managed account. See the Phosra Link Branding Requirement.

What the provider (your partner) must do

Almost nothing new lands on the provider — the signed model is symmetric with what they already sign:
  1. Add your platform DID to their createLink connect target (they call link.connect.finish / link.provision with platformDid: "did:ocss:<you>").
  2. Deliver with their own writer key — no secret from you. If their delivery 401s, the fix is you adding their DID to authorize, not a secret exchange.
  3. That’s it. See Provider · createLink.

Next