Skip to content
Oris Docs

Regulator portal

Goal

Set up regulator-facing access. JWT-scoped read endpoints, HMAC-signed webhooks for SAR-flagged events, threshold decryption for the sealed envelope.

Prerequisites

  • Regulator portal credentials issued by Oris.
  • The regulator’s AGE public key (one of N quorum keys).
  • A webhook endpoint capable of HMAC-SHA256 verification.

Step 1: Mint a scoped JWT

The regulator portal mints JWTs that are scoped to specific tenants and specific actions. The scope is enforced by the audit API.

POST https://api.useoris.xyz/v1/regulator/jwt
{
"tenant_id": "tn_...",
"scope": ["audit.read", "envelope.unseal", "webhook.subscribe"],
"expires_in": 3600
}

The response carries a signed JWT. Send it as Authorization: Bearer <jwt> on subsequent requests.

Step 2: Subscribe to SAR webhooks

POST https://api.useoris.xyz/v1/audit/subscriptions
{
"endpoint": "https://regulator.example.gov/oris-webhook",
"events": ["sar.flagged", "revocation.tier1"],
"secret": "shared_hmac_secret"
}

Every dispatch carries:

X-Oris-Signature: hmac_sha256(secret, timestamp + body)
X-Oris-Timestamp: <unix-epoch>

Five retries with exponential backoff (1m, 5m, 15m, 60m, 240m).

Step 3: Unseal an envelope

Sealed envelopes use AGE threshold encryption. The regulator quorum (typically 3 of 5) signs an unseal request.

# Each regulator member signs the unseal request offline
quorum_signature = quorum_sign(envelope_id, my_age_secret)
# Submit signatures from at least the threshold
plaintext = client.audit.unseal(
envelope_id="env_...",
quorum_signatures=[sig1, sig2, sig3],
)
print(plaintext.bundle)
print(plaintext.kyc_artifacts)
print(plaintext.escalation_approvals)

The plaintext returns only with a valid quorum. Oris cannot read the envelope.

Verification

  • JWT scope check: an out-of-scope call returns 403.
  • Webhook signature: every dispatch verifies HMAC-SHA256 against (timestamp + body).
  • Unseal: requires at least the quorum threshold of valid signatures.

Troubleshooting

  • 403 on audit fetch — the JWT scope does not include audit.read. Re-mint with the correct scope.
  • Webhook signature mismatch — verify the shared secret matches and that timestamp is included in the signed payload.
  • Unseal fails despite threshold — at least one signature is from a key not in the quorum. Check key_id on each signature.

Where to go next