oc · docs
quickstart

orangecheck · Quickstart

Two tracks. Sign in is free and stands on its own: four steps, and your app signs people in and can keep bots out. Pay your users is optional, and you can add it whenever you like: five steps, the last one going live. Nothing here needs a sales call, and nothing is destructive.

Sign in (free)

1 · create a project

Sign in at ochk.app with the account you already have. Your orangecheck account is your developer account. The new project asks for your app's name and domain, how people sign in (choose Use orangecheck sign-in), and when to pay your users. Leave that at Off; the second track turns it on.

name:    Your App
domain:  yourapp.example
→ project key: pk_test_a4f9k2x…

A new project starts in test mode, so its key starts pk_test_. Put it in your server's environment as OC_PROJECT_KEY. Sign-in never needs your domain verified: you verify it only to go live with paying.

2 · add the sign-in popup

yarn add @orangecheck/me-client

Call signInWithOc() inside a click handler, or the browser blocks the popup. The person signs in on orangecheck's page with email, Google, GitHub or a Bitcoin wallet, and the popup hands your page a token bound to your origin. Post it to your own server:

import { signInWithOc } from '@orangecheck/me-client/popup';

document.querySelector('#signin')!.addEventListener('click', async () => {
    const result = await signInWithOc();
    if (!result) return; // closed, or the popup was blocked
    const r = await fetch('/api/auth/session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token: result.token }),
    });
    if (r.ok) location.assign('/dashboard');
});

Your server keeps the token in an httpOnly cookie, out of reach of your page's scripts:

// Next.js Pages · pages/api/auth/session.ts
import type { NextApiRequest, NextApiResponse } from 'next';

import { verifyOcToken } from '@orangecheck/me-client/server';

export default async function session(
    req: NextApiRequest,
    res: NextApiResponse
) {
    if (req.method !== 'POST') return res.status(405).end();
    const token = String(req.body?.token ?? '');
    const s = await verifyOcToken(token, {
        audience: 'https://yourapp.example',
    });
    if (!s) return res.status(401).json({ ok: false });
    const maxAge = (s.exp ?? 0) - Math.floor(Date.now() / 1000);
    res.setHeader(
        'Set-Cookie',
        `oc_session=${token}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`
    );
    res.status(200).json({ ok: true });
}

On Express or Hono it is the same three moves: verify with verifyOcToken, set the cookie, answer ok. To sign someone out, clear the cookie.

<OcSignIn> from @orangecheck/auth-client mounts the sign-in form in your own page instead of a popup. It works only on orangecheck's own sites (*.ochk.io and ochk.app), which share orangecheck's session. Everywhere else, use the popup.

3 · check the session on your server

The wrapper reads the oc_session cookie from step 2.

// Next.js Pages · pages/api/auth/me.ts
import { withOcAuth } from '@orangecheck/me-client/server';

export default withOcAuth(
    async (req, res) => {
        if (!req.ocSession) return res.status(401).json({ ok: false });
        res.status(200).json({
            ok: true,
            account: {
                id: req.ocSession.sub,
                did_oc: req.ocSession.did_oc,
                display_name: req.ocSession.name ?? null,
            },
        });
    },
    {
        audience: 'https://yourapp.example',
        projectKey: process.env.OC_PROJECT_KEY!,
    }
);

Express:

import { ocAuthExpress } from '@orangecheck/me-client/server';
import express from 'express';

const app = express();
app.use(
    ocAuthExpress({
        audience: 'https://yourapp.example',
        projectKey: process.env.OC_PROJECT_KEY!,
    })
);

app.get('/api/auth/me', (req, res) => {
    if (!req.ocSession) return res.status(401).json({ ok: false });
    res.json({
        ok: true,
        account: { id: req.ocSession.sub, did_oc: req.ocSession.did_oc },
    });
});

Hono, Bun, Deno:

import type { OcHonoEnv } from '@orangecheck/me-client/server';

import { ocAuthHono } from '@orangecheck/me-client/server';
import { Hono } from 'hono';

const app = new Hono<OcHonoEnv>();
app.use(
    '*',
    ocAuthHono({
        audience: 'https://yourapp.example',
        projectKey: process.env.OC_PROJECT_KEY!,
    })
);

app.get('/api/auth/me', (c) => {
    const session = c.get('ocSession');
    if (!session) return c.json({ ok: false }, 401);
    return c.json({
        ok: true,
        account: { id: session.sub, did_oc: session.did_oc },
    });
});

audience is your own origin: a token issued to any other site is refused. projectKey makes the wrapper enforce who can sign in (step 4) from your project's settings; a key that names no project answers 500 unknown_project_key. The wrapper fetches and caches orangecheck's keys itself, so there are no JWKs to handle. did_oc is the person's account id, the same in every app.

4 · choose who can sign in

On your project's Sign-in tab, under Who can sign in:

ChoiceWho gets in
Everyone (default)every orangecheck account
Backed by bitcoinaccounts backed by one coin of at least N sats, held at least D days

The coin stays in the person's wallet, and you never see it. A change is enforced within a minute. An account below the bar gets 403 bond_required with a back_url, where the person can back their account. Details, and how to keep one account per coin: Who can sign in.

That is sign-in. It costs nothing, in test mode or live.

Pay your users (optional)

1 · when you pay

On your project's Paying tab, under Pay your users: Off (the default), Always, or Promos, each a named stretch with a start and an end. Outside a pay period an event earns nothing, costs nothing and leaves no record, so sending events from code that runs all the time is safe. Details: Paying your users.

2 · what each event earns

For each event, set what your user earns: a fixed number of sats, or a share of a payment. You pay that ÷ 0.8, the 20% fee included: earns 100, you pay 125. The smallest earning is 1 sat, and you pay 2.

EventGroupYour user earnsYou pay
account_creationOne-time800 sats1,000 sats
payment_authorizationPer action0.4% of the payment0.5% of the payment
session_creationPer session40 sats50 sats

Add your own events too, under any key you choose (lesson_completed, say). Try numbers first in the sandbox. In the config (IntegratorPriceConfig) a price is site_pays, and what your user earns is user_share_pct of it.

3 · send events from your server

import { oc } from '@orangecheck/me-client';

const res = await oc.event.fire({
    project_key: process.env.OC_PROJECT_KEY!,
    subtype: 'account_creation',
    signingSecret: process.env.OC_SIGNING_SECRET,
    accountId: didOc, // who earns: req.ocSession.did_oc from step 3 of sign-in
});
if (!res.earned) {
    // not paying now: nothing recorded or charged
}

Send from your backend, never the browser. The signing secret, on your project's Keys & webhooks tab, proves the call comes from your project. Every paid event gets a receipt anyone can check offline against Bitcoin: see an example receipt.

4 · send a test event

On your project, Send a test event sends a sample event under your project key through the same pipeline as a real one. It is stored with your project's events, delivered to your webhooks, and gets a receipt you can open. It records whether or not you are paying now. Nobody is paid.

5 · go live

Live mode needs three things, and your project shows each one:

✓ domain verified
✓ balance funded
· settlement open

Paying is in testing: settlement is not open for any project yet, so no real sats move. Until it opens, every event is signed and checkable, and your project's page shows when that changes.

To verify your domain, add either record, then check it from your project:

# DNS TXT at yourapp.example (apex)
oc-verify=<token>

# OR a meta tag in <head> at https://yourapp.example/
<meta name="oc-domain-verify" content="<token>" />

See it working

me-demo.ochk.io is an integration we built ourselves: a fictional newsletter site (Lumen Notes), styled deliberately unlike orangecheck. Its sign-in button is the signInWithOc() popup from step 2. Source at github.com/orangecheck/oc-me-demo-web.

Next

  • SDK reference: every export, with types
  • Integrations: the sign-in button beside your other providers, example prices, errors
  • Webhooks: receiving events, delivery and retries
  • HTTP API: every me.ochk.io/api/* endpoint
  • Glossary: the words the product uses