Six flows. The mechanic that enforces every commitment.
The integrator's due-diligence walkthrough. Envelope lifecycle, the four-way fee split, federation custody (what your users sit on by default), cross-subdomain SSO, the three event classes A/B/C, and the offline verification recipe. Every section ends with the charter commitment that mechanic enforces — the ethos isn't marketing copy, it's a published constant + a cryptographic primitive.
envelope lifecycle · per billable event
What happens, end-to-end, when one of your users does something billable on your site. Six steps. Every step is content-addressed; every step verifies offline against Bitcoin headers. This is the mechanic your CFO and compliance reviewer should walk through before you sign up.
- 01 · event happens at your gateA session opens, a payment authorizes, an attest verifies. Your code calls the SDK (oc.session.create / oc.payment.authorize / oc.event.fire) with the subtype and any required context (e.g. payment_amount_sats for percent-of-amount events).
- 02 · envelope is canonicalized + signedFields are ordered per RFC 8785, hashed (SHA-256), signed with OC's Ed25519 envelope key on your project's behalf (kid published at me.ochk.io/.well-known/oc-envelope-jwks.json). The signature commits to gross_fee_sats — the amount you fund — derived from your IntegratorPriceConfig + the event subtype. You host no key and deploy no file.
- 03 · your project optionally co-signsIf you registered a public Ed25519 JWK (Keys tab · OC hosts it for you), OC adds your project co-signature so third parties can verify the envelope against your own key too. Skip it and the integration runs on your HMAC signing_secret alone.
- 04 · four-way fee split appliedplatform_fee_sats = gross_fee_sats × 0.20 (fixed; ratified in charter). user_earned_sats = (gross_fee_sats - platform_fee) × your user_share_pct. site_rebate_sats = the residual — yours to spend on premium-tier cashback or withdraw.
- 05 · envelope is OTS-anchored + Nostr-publishedHash is submitted to an OpenTimestamps calendar by the /api/cron/anchor sweeper (every 15 minutes); calendars batch the digest into a Bitcoin block, typically within an hour, after which the proof "upgrades" to a real block anchor. Envelope is republished to four public Nostr relays as kind-30087 (d-tag prefix oc-me-event:<id> / oc-me-payment:<id> / oc-me-rebind:<id>). Both are independent of OC infrastructure — receipts continue verifying if OC vanishes.
- 06 · user sees cashback · you see the eventUser's /me/earn shows the credit with a /verify/[id] link. Your /me/projects/[id]/events shows the same envelope. Anyone with the envelope id can re-derive the content hash + verify the signature against me.ochk.io/.well-known/oc-envelope-jwks.json — no OC servers required.
# what your project signs · class C session_creation
{
"v": 1,
"kind": "oc-me-event",
"id": "oc-me-9k2x4f7p",
"class": "C",
"subtype": "session_creation",
"identity": "bc1q...e3lr", // your user's oc identity
"site": "yourcompany.com", // your project domain
"session_duration_seconds": 604800, // 7 days
"occurred_at": "2026-04-30T16:11:08Z",
"gross_fee_sats": 64, // funded by your project
"platform_fee_sats": 13, // 20% to OC (charter §07)
"user_earned_sats": 33, // 65% of post-platform 80%
"site_rebate_sats": 18, // your residual
"sig": "ed25519:2b:9pK4...rT8x" // signed by OC on your behalf
}the four-way fee split, concretely
Where each sat lands on a billable envelope. The math is published, the platform fee constant is in lib/events/types.ts (PLATFORM_FEE_POLICY.pct = 0.20), and computeFees() is the canonical function — same one the SDK runs locally for live form feedback.
- 01 · gross_fee_sats · 100% · you fund thisPulled from your IntegratorPriceConfig per-subtype value. Either fixed sats (account_creation, session_creation, attest_bond_increased) or a percent of an underlying amount (payment_authorization, attest_verification_at_gate).
- 02 · platform_fee_sats · 20% · OC keepsFixed by charter; changeable only via a versioned @orangecheck/me-client release with governance ratification. Funds custody operations, signing service, OTS anchor pipeline, abuse review queue, federation guardian compensation.
- 03 · user_earned_sats · your user_share_pct of the 80%Range 0–80%. You set it per subtype. 0% = you keep all the post-platform cashback as rebate. 80% = your users get nearly every sat. Typical defaults: 65% on actions, 70% on payments, 50% on state transitions.
- 04 · site_rebate_sats · the residualWhat's left after platform_fee and user_share. Lands in your project's OC balance. Use it for premium-tier cashback experiments, retention promotions, or just withdraw it monthly via lightning or stripe.
# computeFees() · the canonical formula
// in lib/events/types.ts (CI-tested, conformance-vector-locked)
const PLATFORM = 0.20;
function computeFees(cfg, payment_amount_sats?) {
const gross = cfg.site_pays.kind === 'fixed_sats'
? cfg.site_pays.sats
: Math.round(payment_amount_sats * cfg.site_pays.pct);
const platform = Math.round(gross * PLATFORM);
const post = gross - platform;
const user = Math.round(post * cfg.user_share_pct);
const rebate = post - user;
return { gross, platform, user, rebate };
}
// example · gross 1000, user_share 0.65
// → platform 200, user 520, rebate 280federation custody · what your users sit on
Your users don't need to know what bitcoin is to use your sat-paying sign-in. We provision a federation-custodied lightning wallet behind their oc identity by default. Self-custody graduation is a one-click sweep when they're ready. Integrator-side, you have no custody — your project signs with its own keypair.
- 01 · user signs up via your siteBoth signup paths land on the same opaque did:oc identity — public identifier is a random 32-hex DID, unaffected by which path you took. BIP-322 wallet path: the user signs a challenge with their wallet; their address goes into linked_identities (plaintext, since BTC addresses are public on-chain). Email-OTP path: the email lands in linked_identities AES-256-GCM encrypted at rest. Federation-held wallet provisions client-side on first /me/wallet visit (oc-me-1 has completed DKG and is live) — mnemonic in browser IndexedDB, OC never sees keys or mnemonic.
- 02 · cashback streams to their custody modeFederation-custodied users see their balance at /me/wallet, can spend via lightning, can withdraw on-chain. Self-custody users get the cashback paid directly to their wallet via lightning.
- 03 · graduation is one envelope/me/graduate flow: federation guardians collectively sign the sweep transaction; user watches it confirm on a Bitcoin block. The oc identity's signing_method discriminator flips from federation_threshold to bip-322. Everything else continues — your project, your envelopes, their attest tier.
- 04 · no custody on the integrator side · everYou host nothing. Request auth is an HMAC secret OC issues (sign each event POST with it · zero deploy). OC signs the envelopes and reconciles the four-way split. Envelope co-signing is optional — register a public Ed25519 JWK and OC hosts it at me.ochk.io/api/integrator/<key>/jwks.json; the private half stays with you. OC never holds your private keys.
# the federation descriptor · published per-user
{
"v": 1,
"identity": "bc1q...e3lr",
"signing_method": "federation_threshold",
"guardian_set_id": "fed11qgqrgvnhwd...",
"threshold": "5-of-7",
"graduation_path": [
"stay-federation",
"graduate-to-fedimint",
"graduate-to-self-custody"
]
}
# after /me/graduate · same identity, new method
{
"v": 1,
"identity": "bc1q...e3lr", // unchanged
"signing_method": "bip322",
"graduated_at": "2026-05-15T09:21:00Z",
"previous_method": "federation_threshold"
}cross-subdomain SSO · one identity, every site
Auth is hosted at ochk.io. It issues an Ed25519-signed oc_session JWT cookie scoped to Domain=.ochk.io. Every consumer subdomain — me, attest, lock, vote, stamp, agent, pledge, fleet — verifies it locally against the published JWKS. Your integration calls the same useOcSession() / oc.session.create() everywhere.
- 01 · auth host issues the oc_sessionochk.io/signin runs the BIP-322 challenge for advanced users; ochk.io/api/auth/email-otp/{start,verify} runs the email-OTP flow for everyone else (CORS-enabled so consumer subdomains can call it cross-origin from in-place forms — no redirect required). Either path mints an Ed25519-signed JWT with claims { sub, addr, npub?, iat, exp }, sets it as a cookie with Domain=.ochk.io, HttpOnly, SameSite=Lax.
- 02 · every consumer subdomain reads it locallyYour me.ochk.io integration uses @orangecheck/auth-client; the SDK's OcSessionProvider validates the JWT against ochk.io/.well-known/jwks.json (cached 1h). No round-trip to ochk.io per request — verification is local + cryptographic.
- 03 · session policies are integrator-setYou declare session_policy at oc.session.create({ duration_seconds, refresh: "sliding" | "fixed" }). Banking pattern: 15-minute sliding window. SaaS pattern: 7-day sliding. Mobile pattern: 90-day fixed. Class C billable on creation, free on refresh + invalidate.
- 04 · sign-out propagates · revocation is immediateYour oc.session.invalidate() call (or the user signing out at any sibling site) clears the cookie family-wide. Subsequent envelope signing fails on every subdomain instantly — no replay window beyond the cookie's already-active lifetime.
# oc_session JWT · what every consumer subdomain reads
{
"alg": "EdDSA",
"kid": "oc-auth-1",
"sub": "did:btc:bc1q...e3lr",
"addr": "bc1q...e3lr",
"npub": "npub1...8q9w",
"iat": 1714512000,
"exp": 1715116800,
"scope": ["identity", "payment"],
"session_policy": {
"duration_seconds": 604800,
"refresh": "sliding"
}
}
# JWKS published at ochk.io/.well-known/jwks.json
# verified locally · no ochk.io round-trip per requestthree event classes · A · B · C
Every billable subtype falls into one of three classes. The class determines pricing dial range and the computeFees() behavior. Classes are not editable per integrator — they're SDK-level invariants, defined in @orangecheck/me-client/types and enforced by SUBTYPE_CLASS in lib/events/types.ts.
- 01 · class A · state transitionsaccount_creation, attest_bond_increased, agent_delegation_issued, recovery_method_updated, payment_method_connected. Fixed-sat priced. One-time per state change. Used to reward sybil-resistant signup, bond attestation (BIP-322 sat-bond, no KYC), agent authority registration. Typical range: 200–5,000 sats.
- 02 · class B · action-boundpayment_authorization, scoped_action_authorization, attest_verification_at_gate, stamp_signing, pledge_resolution. Fixed sats OR percent_of_amount. Per action. The dominant earnings line on most integrators. Typical: 0.5–1.5% on payments, 50–500 sats on non-monetary actions.
- 03 · class C · sessionssession_creation. Fixed-sat priced. Per session, NOT per click. Sign-ins inside a valid session are free (telemetry-only). Typical: 5–80 sats per session. A `lightning_received` subtype for sat-paid Lightning credits is on the spec roadmap; current Class C handles the session case.
- 04 · computeFees signature varies by classClass A + C: computeFees(cfg). Class B percent_of_amount: computeFees(cfg, payment_amount_sats) — the second arg is required, validated server-side, and recorded in the envelope. Conformance test vectors in @orangecheck/me-client lock the rounding rules.
# subtype → class mapping · in lib/events/types.ts
// CI-tested · ratified · changeable only via spec PR
export const SUBTYPE_CLASS: Record<EventSubtype, EventClass> = {
// Class A — state transitions
account_creation: 'A',
account_recovery: 'A',
attest_bond_increased: 'A',
payment_method_connected: 'A',
agent_delegation_issued: 'A',
recovery_method_updated: 'A',
// Class B — action-bound
payment_authorization: 'B',
scoped_action_authorization:'B',
attest_verification_at_gate:'B',
stamp_signing: 'B',
pledge_resolution: 'B',
// Class C — sessions
session_creation: 'C',
};audit + offline verification
Five years from now, your envelopes still verify. The protocol is the API; OC's servers are not in the verification path. This is the load-bearing reason any compliance team should be willing to sign off — your receipts outlast OC.
- 01 · fetch the canonicalized envelopecurl https://me.ochk.io/api/envelope/<id> returns the RFC-8785-canonical JSON. The hash you compute over those bytes is the content address.
- 02 · verify the OC signatureFetch me.ochk.io/.well-known/oc-envelope-jwks.json. Verify(pubkey, sigHex, sha256(canonicalJson)) against the envelope's sig + kid. If it passes, OC accepted the envelope and committed to the four-way split. This is the load-bearing signature.
- 03 · verify the project co-signature (optional)Only present if the project opted into co-signing. Fetch its OC-hosted JWKS at me.ochk.io/api/integrator/<project_key>/jwks.json (the integrator hosts nothing) and run the same Verify(). An empty key set just means the project authenticates with its HMAC secret instead.
- 04 · walk the OTS proof to a Bitcoin blockThe envelope carries an OpenTimestamps proof. Run `ots verify envelope.ots` against any synced Bitcoin node. The proof terminates in a real block hash — that's the timestamp guarantee, independent of OC.
- 05 · check Nostr publicationThe envelope is published as a kind-30087 event on four public relays (relay.damus.io, relay.nostr.band, nos.lol, relay.snort.social) — d-tag prefix oc-me-event:<id> / oc-me-payment:<id> / oc-me-rebind:<id> per envelope kind. Signed under the me.ochk.io publishing pubkey at /.well-known/oc-nostr-pubkey.json. Any Nostr client can fetch it. Independent of OC, independent of any single relay operator.
# verify any envelope offline · ~30 lines of node
import { ed25519 } from '@noble/curves/ed25519';
import { sha256 } from '@noble/hashes/sha256';
import { canonicalize } from '@orangecheck/me-client';
const envelope = await fetch('https://me.ochk.io/api/envelope/' + id).then(r => r.json());
const canonical = canonicalize(envelope);
const hash = sha256(new TextEncoder().encode(canonical));
// 1. OC signature · the load-bearing one
const ocJwks = await fetch('https://me.ochk.io/.well-known/oc-envelope-jwks.json').then(r => r.json());
const ocKey = ocJwks.keys.find(k => k.kid === envelope.kid);
const ocOk = ed25519.verify(parseSig(envelope.sig), hash, parseJwk(ocKey));
// 2. project co-signature · optional, OC-hosted (integrator hosts nothing)
const projectJwks = await fetch(`https://me.ochk.io/api/integrator/${envelope.project_key}/jwks.json`).then(r => r.json());
const projectOk = projectJwks.keys.length === 0
? null // project uses its HMAC secret · no co-sig to verify
: ed25519.verify(parseSig(envelope.project_sig), hash, parseJwk(projectJwks.keys[0]));
// 3. ots proof → bitcoin
// $ ots verify envelope.ots
// Success! Bitcoin block 879543 attests existence as of 2026-04-30 16:11:08 UTC
console.log({ projectOk, ocOk });where the spec lives · what to read next
The protocol-layer details — RFC 8785 canonicalization, Ed25519 JWK formats, OTS proof envelope format, Nostr kind-30087 conventions, every conformance vector — live at docs.ochk.io/me. The full SDK reference is at docs.ochk.io/me/sdk. The charter (eight commitments) is at docs.ochk.io/charter.
Ready to ship? /why#integrators has the live configurator, the cost-model sandbox, and the three integration pathways (drop-in, SDK, co-branded). /me/projects/new is the three-step onboarding wizard.