orangecheck · Release schedule
When your users' shares are theirs. By default, each event's share is your
user's right away. You can release shares in batches instead: weekly,
monthly, or when you say. Events in a batch are priced and paid for exactly as
before, from your balance; only the moment the share becomes ready to cash out
moves to the end of the batch. In the API a batch is a drop (drops,
drop.closed), and its open stretch is a window.
| Release | When shares are ready |
|---|---|
| Right away | as each event lands (the default) |
| In batches | at the end of each batch: weekly, monthly, or when you say |
Three rules make batches safe for the people who earn:
- No clawback, ever. A share waiting in a batch is already earned and already paid for. A batch only delays when it is ready, never whether.
- Ends only move earlier. Once a batch opens, its end can't be pushed later. "Release now", turning batches off, or deleting a schedule releases the open batch at once. Changes to cadence or events apply from the next batch.
- A published ceiling. No schedule may hold earnings longer than
MAX_DROP_WINDOW_DAYS(92), published inABUSE_LIMITSand on the trust page.
Cadences
| Batches | API cadence | A batch ends | Use |
|---|---|---|---|
| Weekly | weekly | a fixed weekday + hour, UTC | "the Friday payout" |
| Monthly | monthly | a fixed day (1–28) + hour, UTC | payroll-shaped payouts |
| When you say | manual | when you say (auto-closes ≤ 92 days) | a campaign, a launch |
Set a schedule on your project, under Release schedule, or read schedules publicly:
const r = await fetch(
'https://me.ochk.io/api/integrator/config?project_key=pk_live_yourco'
).then((x) => x.json());
// r.drops → [{ schedule_id, label, subtypes, cadence }]
An event belongs to at most one active schedule. Events you don't cover are ready right away.
The end of a batch is a Bitcoin block
When a batch opens, the cadence compiles to a target close height from the observed chain tip (mempool.space, blockstream.info fallback — the source is named in the manifest). Shares are ready when the tip reaches that height. If no tip is observable, the batch falls back honestly to its clock time — and regardless of everything else, a published 24h safety valve past the scheduled end releases it unconditionally, so neither an orangecheck outage nor a chain-tip-source outage can freeze earners' money.
What your code sees
Event responses carry a drop block when the event is in a batch (top-level on
/api/integrator/event, per-result on /event/batch):
import type { DropResponseBlock } from '@orangecheck/me-client';
// {
// window_id: 'w_…',
// label: 'friday drop',
// state: 'open' | 'closing', // 'closing' = boundary passed, sweep pending (~15 min)
// closes_at_target: '2026-06-12T18:00:00.000Z',
// close_height_target: 901008 // null when no tip was observable at open
// }
The event's signed canonical bytes (schema v=4) commit to
{ window_id, close_height_target } — being in a batch is cryptographic, not a
mutable database claim.
When a window closes, subscribed webhooks receive a drop.closed system event:
import type { DropClosedWebhookPayload } from '@orangecheck/me-client';
// kind: 'oc-system-event', subtype: 'drop.closed',
// window_id, schedule_id, label, manifest_id, verify_url,
// sealed_at, sealed_height, event_count, recipient_count,
// gross_fee_sats, user_share_sats
Subscribe with '*' or the explicit 'drop.closed' string, same as the balance
alerts.
The manifest · what anyone can check
Every finished batch publishes a signed, content-addressed oc-me-drop manifest
(OTS-anchored, republished to Nostr kind 30087):
- the id is the sha256 of the canonical bytes; the Ed25519 signature verifies against me.ochk.io's published envelope JWKS;
events_rootis a Merkle root over the member events' canonical digests — any member event opens against it with a log-size proof;recipients_rootis a Merkle root over per-window-salted(address, sats)leaves — each recipient can prove their line without the cohort being enumerable;- the sealed block height and its source are named.
Render + live-verify at ochk.app/verify/<manifest_id>; raw canonical bytes at
me.ochk.io/api/envelope/<manifest_id> (add ?format=wrapped for a
self-contained record). Signed-in earners fetch their inclusion proof from
GET /api/me/drops/proof?window_id=….
Named trust, stated plainly: the completeness of the member set is OC's assembly from its event store — inclusion is provable, omission is not. That is the same trust boundary every receipt store in the product carries, and it's written on the verify page rather than hidden.
What earners see
Their balance splits Ready to cash out from Waiting (with its date), lists each upcoming batch with its end (block and approximate time), and names your app as the one that set the schedule. Released sats pay out the normal way: to where the person set their sats to go, or when they cash out. Word your copy accordingly: a batch is "when earnings are ready", not "when sats reach their wallet".
Why you'd use it
- Legible payouts — a hundred tiny session events become one weekly line the earner actually notices.
- A payout moment — one announceable release your product can build a ritual around.
- Budget cadence — your balance is still charged per event, but what earners see happens on a schedule you publish.