oc · me
§ how it works · mechanically

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.

[01]

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.

  1. 01 · event happens at your gate
    A 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).
  2. 02 · envelope is canonicalized + signed
    Fields 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.
  3. 03 · your project optionally co-signs
    If 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.
  4. 04 · four-way fee split applied
    platform_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.
  5. 05 · envelope is OTS-anchored + Nostr-published
    Hash 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.
  6. 06 · user sees cashback · you see the event
    User'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.
enforces · Commitment 05 · Audit verifies without us. Every step in this lifecycle is reproducible offline. docs.ochk.io/charter ↗
flow · 01
# 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
}
[02]

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.

  1. 01 · gross_fee_sats · 100% · you fund this
    Pulled 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).
  2. 02 · platform_fee_sats · 20% · OC keeps
    Fixed 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.
  3. 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.
  4. 04 · site_rebate_sats · the residual
    What'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.
enforces · Commitment 01 · No token, ever (sats only) + Commitment 07 · Sat-denominated, first-class. docs.ochk.io/charter ↗
flow · 02
# 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 280
[03]

federation 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.

  1. 01 · user signs up via your site
    Both 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.
  2. 02 · cashback streams to their custody mode
    Federation-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.
  3. 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.
  4. 04 · no custody on the integrator side · ever
    You 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.
enforces · Commitment 02 · No custody you didn't choose. Federation default is opt-in; graduation is one click. docs.ochk.io/charter ↗
flow · 03
# 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"
}
[04]

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.

  1. 01 · auth host issues the oc_session
    ochk.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.
  2. 02 · every consumer subdomain reads it locally
    Your 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.
  3. 03 · session policies are integrator-set
    You 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.
  4. 04 · sign-out propagates · revocation is immediate
    Your 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.
enforces · Commitment 03 · The protocol stays open. JWT shape + JWKS endpoint are spec-defined. docs.ochk.io/charter ↗
flow · 04
# 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 request
[05]

three 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.

  1. 01 · class A · state transitions
    account_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.
  2. 02 · class B · action-bound
    payment_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.
  3. 03 · class C · sessions
    session_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.
  4. 04 · computeFees signature varies by class
    Class 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.
enforces · Commitment 03 · The protocol stays open. Subtype-class mapping is in the spec. docs.ochk.io/charter ↗
flow · 05
# 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',
};
[06]

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.

  1. 01 · fetch the canonicalized envelope
    curl https://me.ochk.io/api/envelope/<id> returns the RFC-8785-canonical JSON. The hash you compute over those bytes is the content address.
  2. 02 · verify the OC signature
    Fetch 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.
  3. 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.
  4. 04 · walk the OTS proof to a Bitcoin block
    The 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.
  5. 05 · check Nostr publication
    The 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.
enforces · Commitment 05 · Audit verifies without us. The whole pipeline is offline-reproducible. docs.ochk.io/charter ↗
flow · 06
# 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.