@orangecheck/me-client · SDK reference
v0.7.0 · MIT.
This is the canonical TypeScript SDK for integrating me.ochk.io. Three entry points, three audiences:
@orangecheck/me-client— React surface (provider + hook + button).@orangecheck/me-client/server— server-side verification.withOcAuth(Next.js Pages),ocAuthExpress,ocAuthHono,getOcSession(headers),verifyOcToken(token). No env vars, no JWK handling, no/.well-known/jwks.jsonfetching by you — the SDK does that internally and caches.@orangecheck/me-client/popup— browser-only.signInWithOc()opens the OC popup, listens for postMessage, returns the verified session result. The same shape Google / GitHub use.
install
yarn add @orangecheck/me-client @orangecheck/auth-client
@orangecheck/auth-client is the React provider + hook. me-client re-exports
it from the main entry point, so most React code only needs to import from
me-client. Server-side code reaches for @orangecheck/me-client/server and
never touches React.
server-side verification (zero JWK handling)
withOcAuth for Next.js Pages Router:
// 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,
btc_address: req.ocSession.addr,
display_name: req.ocSession.name ?? null,
nostr_npub: req.ocSession.npub ?? null,
},
});
});
ocAuthExpress for Express:
import { ocAuthExpress } from '@orangecheck/me-client/server';
import express from 'express';
const app = express();
app.use(ocAuthExpress());
app.get('/api/profile', (req, res) => {
if (!req.ocSession) return res.status(401).json({ error: 'sign in' });
res.json({ address: req.ocSession.addr });
});
ocAuthHono for Hono / Cloudflare Workers / Bun / Deno:
import { ocAuthHono } from '@orangecheck/me-client/server';
import { Hono } from 'hono';
const app = new Hono();
app.use('*', ocAuthHono());
app.get('/api/me', (c) => {
const session = c.get('ocSession');
if (!session) return c.json({ ok: false }, 401);
return c.json({ address: session.addr });
});
getOcSession(headers) is the framework-agnostic primitive — accepts a Web
Headers object or a plain { cookie, authorization } object. Returns the
verified SessionPayload or null. Cookie auth (family) and Bearer auth
(cross-domain) are handled identically.
The wrappers all take an options bag: { required: true } short-circuits
unauthenticated requests with a 401 before your handler runs.
browser popup signin
import { signInWithOc } from '@orangecheck/me-client/popup';
button.addEventListener('click', async () => {
const result = await signInWithOc();
if (!result) return; // user cancelled or popup blocked
localStorage.setItem('oc-token', result.token); // optional · for cross-domain
location.assign('/dashboard');
});
signInWithOc() MUST be called inside a user-gesture handler (browsers block
window.open outside of gestures). Returns { account, token } on success,
null on cancel / popup block / abort.
React surface
<OcSessionProvider>
Wrap your app once at the root. Default authOrigin is https://ochk.io.
import { OcSessionProvider } from '@orangecheck/me-client';
<OcSessionProvider>{/* … */}</OcSessionProvider>;
useOcSession() and useOptionalOcSession()
Returns { status, account, signInUrl, signOut, refresh, error }. Status is
loading | authenticated | anonymous | error. Account is null when not
authenticated.
import { useOcSession } from '@orangecheck/me-client';
function Header() {
const { status, account, signInUrl, signOut } = useOcSession();
if (status === 'loading') return <Skeleton />;
if (status !== 'authenticated') return <a href={signInUrl}>sign in</a>;
return (
<div>
signed in as {account.address.slice(0, 8)}…
<button onClick={() => signOut()}>sign out</button>
</div>
);
}
useOptionalOcSession() returns null instead of throwing — useful for libraries
that want to read the session if it exists but shouldn't crash on apps that
haven't mounted the provider.
oc.session.* · session lifecycle (Class C billable atom)
Sites pay per session, not per click. create() opens a new session;
refresh() and invalidate() are free, telemetry-only.
import { oc } from '@orangecheck/me-client';
const session = await oc.session.create({
scope: ['identity', 'payment'],
sessionPolicy: { duration_seconds: 7 * 86400, refresh: 'sliding' },
});
await oc.session.refresh(session.id);
await oc.session.invalidate(session.id);
| Function | Signature | Note |
|---|---|---|
oc.session.create(opts) | (SignInOptions) => Promise<Session> | Class C billable. |
oc.session.refresh(id) | (string) => Promise<Session> | Free. |
oc.session.invalidate(id) | (string) => Promise<void> | Free. |
oc.payment.authorize · Class B billable atom
const result = await oc.payment.authorize({
identity: 'bc1q...',
amount_sats: 240_000,
description: 'breez · march invoice',
});
// result.id, result.user_envelope_id, result.verify_url
The user is prompted by me.ochk.io to consent before this resolves.
oc.config.validate · local IntegratorPriceConfig validator
Pre-flight your config against the same rules the server enforces — no network
call, no round-trip. Per-project read/write is owner-gated and lives at
/api/me/projects/[id] (or
me.ochk.io/me/projects/<key> in the UI);
the SDK intentionally doesn't include a global oc.config.update()
because configs are per-project, not per-account.
import { oc } from '@orangecheck/me-client';
import type { IntegratorPriceConfig } from '@orangecheck/me-client';
const cfg: IntegratorPriceConfig = {
/* … */
};
const result = oc.config.validate(cfg);
if (!result.ok) console.error(result.errors);
| Function | Signature | Note |
|---|---|---|
oc.config.validate(cfg) | (IntegratorPriceConfig) => ValidationResult | No network call. |
The same function is also exported as the top-level validateIntegratorConfig.
oc.webhook.verify · Ed25519 signature verification
Always pass the raw body bytes, not the parsed JSON. Frameworks that re-serialize before your handler produce a different byte sequence and the signature will not validate.
const result = await oc.webhook.verify(
rawBody, // string | Uint8Array
sigHex, // OC-Signature header
kid // OC-Key-Id header
);
if (!result.ok) return res.status(401).end(result.reason);
Auto-fetches and caches ochk.io/.well-known/jwks.json for 1h when no jwk is
passed. Stale-on-error: a transient JWKS outage returns the previous cached
entry rather than rejecting every webhook.
Agent delegation (
oc.delegation.*) is a separate primitive and lives on agent.ochk.io / docs.ochk.io/agent, not on me.ochk.io. me-client doesn't ship a delegation namespace; reach for@orangecheck/agent-clientif you need to issue or revoke agent delegations from your integration.
oc.event.fire · arbitrary billable subtypes
The escape hatch when you want to bill a subtype that isn't covered by
oc.session.create or oc.payment.authorize — stamp_signing,
attest_verification_at_gate, scoped_action_authorization,
kyc_tier_upgrade, etc. Class is determined by SUBTYPE_CLASS; cashback is
computed via computeFees(); the envelope is recorded under your project_key
and shows up in /developer/projects/[id]/events.
const stamp = await oc.event.fire({
project_key: 'pk_live_yourcompany',
subtype: 'stamp_signing',
action_label: 'review · march invoice',
});
// stamp.id, stamp.gross_fee_sats, stamp.user_earned_sats, stamp.verify_url
// For percent_of_amount-priced subtypes, include the underlying amount:
const verify = await oc.event.fire({
project_key: 'pk_live_yourcompany',
subtype: 'attest_verification_at_gate',
payment_amount_sats: 50_000,
});
| Function | Signature | Note |
|---|---|---|
oc.event.fire(opts) | (FireEventOptions) => Promise<BillableEvent> | Returns the canonical BillableEvent the server recorded. |
Types
Every type the SDK exports:
EventClass · EventSubtype · ClassASubtype · ClassBSubtype · ClassCSubtype
AttestTier · SiteFeeShape · IntegratorEventConfig · IntegratorPriceConfig
ComputedFees · ValidationResult · BillableEvent · Session · SessionPolicy
SignInOptions · PaymentAuthorizeOptions · PaymentResult · TelemetryEvent
FireEventOptions · OcPublicJwk · VerifyOptions · VerifyResult
@orangecheck/me-client re-exports the React surface from
@orangecheck/auth-client, so OcAccount, OcSessionState, OcSessionStatus,
and OcAuthConfig are available at runtime via that re-export — import them
directly from @orangecheck/auth-client for stable typed access.
Constants and helpers
| Export | Signature |
|---|---|
PLATFORM_FEE_POLICY | { pct: 0.2; min_floor_sats: 1; ratified: string } |
MIN_INTEGRATOR_PRICE_SATS | 5 |
computeFees(cfg, payment_amount_sats?) | (IntegratorEventConfig, number?) => ComputedFees |
validateIntegratorConfig(cfg) | (IntegratorPriceConfig) => ValidationResult |
setBearerToken(token) | (string | null) => void · cross-domain auth |
getBearerToken() / clearBearerToken() | () => string | null / () => void |
Where it lives
- npm: @orangecheck/me-client
- source: oc-packages/me-client
- canonical type module:
@orangecheck/me-client/types(mirror ofoc-me-web/src/lib/events/types.ts)