Custody & compliance
0Bit's trust and responsibility model — why 0Gate is non-custodial for partners, what 0Bit owns on every tier (KYC, AML, fiat rails, on-chain settlement), and the crisp line between what you build and what we run.
0Gate is non-custodial for you, the partner: 0Bit never holds your funds — or your end-users' funds — as a balance you draw down. There is no partner wallet to top up and no float sitting on a 0Bit ledger in your name. Crypto settles directly to the destination wallet and fiat settles to the destination bank account; 0Bit is the regulated operator that moves money between those two endpoints for a single checkout, then tells you the outcome by webhook. Everything that makes that movement legal and safe — identity, screening, fraud, the fiat rails, and the on-chain leg — is 0Bit's job, on every tier. You bring your app, your users, and the page the widget sits in. You never rebuild the ramp UI or the compliance stack.
This page is the mental model for that boundary. The how-to lives in Authentication and the 0Gate product guide; here we explain why the line sits where it does and who owns what.
Non-custodial, precisely
"Non-custodial" is an overloaded word, so here is exactly what it means for a 0Gate integration:
- There is no partner balance. You do not pre-fund 0Bit and spend down a float. Each session is a single, self-contained checkout with its
amountandcurrencybound server-side at create time. - Funds settle to endpoints, not to a 0Bit account in your name. On a buy, the user's fiat enters 0Bit's regulated rails and crypto lands at the destination wallet for that session. On a sell, crypto enters and fiat settles to the destination bank account. 0Bit is the operator of record in the middle of one transaction — not a custodian of a standing balance for you.
- You don't touch the money leg. Your server creates a session and receives signed webhooks. It never holds keys to a 0Bit wallet, never initiates a pay-out, and never reconciles the on-chain or bank legs. That is all inside 0Bit.
Non-custodial for the partner ≠ self-custody for the user
0Gate is non-custodial in the sense that you never hold a balance and never touch the money rails. As the regulated operator executing the conversion, 0Bit necessarily takes momentary control of funds in flight (that's what a licensed ramp does). The point is that the partner carries no custody, no float, and no settlement responsibility.
How funds flow (non-custodial)
A 0Gate checkout has a fiat leg and a crypto leg, and they meet inside the operator — never inside your application. The direction of each leg is fixed by the session's flow, which the API enums as on_ramp (buy), off_ramp (sell), or swap.
Buy (on-ramp): fiat in, crypto out
- Your server creates a session (
POST /v1/gate_sessions,flow: on_ramp) withamount+currencybound. You may pre-fill the destinationwallet_address. - Inside the iframe the user is verified (if needed) and chooses a payment method. The user's fiat pay-in enters 0Bit's regulated rails — SEPA bank transfer for EUR/DKK, and local rails for LatAm (PIX and BOLETO in Brazil, SPEI in Mexico, PSE in Colombia, plus CARD).
- 0Bit executes the on-chain leg: crypto is sent to the destination wallet for that session, on the resolved network.
- When the underlying transaction succeeds, 0Bit emits
gate_session.completedto yourwebhook_url, carryingdata.tx_refidso you can join back to your order.
Sell (off-ramp): crypto in, fiat out
- Session created with
flow: off_ramp. - The user sends crypto into 0Bit. 0Bit confirms the on-chain receipt.
- 0Bit pays fiat out to the user's destination bank account over the same regulated rails (e.g. a SEPA payout for EUR).
gate_session.completedfires on settlement.
Swap: crypto in, crypto out
A swap session takes crypto in and delivers crypto out, routed through 0Bit's liquidity. The same session lifecycle and webhook events apply; there is no partner-held intermediate balance at any step.
The amount is locked at the operator, not the browser
amount and currency are bound when your server creates the session and are enforced by the embed token on every downstream call. The user can pick a token, a network, or a payment method inside the iframe, but cannot change the bound value. A leaked publishable key cannot move money or alter the amount — see the boundary holds on every tier.
What 0Bit owns
The single most important thing to internalize: 0Bit owns the user of record, KYC, AML/compliance, the fiat rails, and on-chain settlement — on every tier. Partners never rebuild the ramp UI or the compliance stack. The subsections below name the real systems behind each area so you know exactly where the line falls.
Identity / KYC
Identity verification runs entirely inside the 0Bit iframe against 0Bit's verification provider. You never collect, store, screen, or brand identity documents, and you cannot bring your own KYC vendor through 0Gate.
- A verification session is created server-side against 0Bit's verification provider, keyed by an opaque
vendor_data(the 0Bit user id) that is echoed back in the result webhook. - The provider posts a decision back to 0Bit's callback. The decision carries sub-checks for
id_verification(name, DOB, document images, issuing state),aml,face_match,liveness, andpoa(proof of address). 0Bit maps an overallApprovedstatus onto the user profile; any non-approved status is recorded without unlocking the rails. - The verified profile — not your partner key — is what authorizes a money-moving flow. KYC PII (DOB, document images, address) is never logged in full and is never echoed in any API response or webhook payload.
Pre-verified KYC for trusted partners
If you are a contractually kyc_trusted partner, you may submit your own verification result on session create via the opaque kyc_package object (which may include a provider string — recommended, for audit). When accepted:
- The session is created with
kyc_pre_verified: true, forwarded to the iframe so it can route around its own KYC step. - A redacted
gate_session.kyc_package_acceptedwebhook is delivered — it carries onlysession_id,partner_id,mode,provider,accepted_at, anduser_reference. The rawkyc_packagebody is never echoed back. - Submitting a
kyc_packagewithout thekyc_trustedflag returns403withcode: kyc_package_not_trusted.
You do NOT rebuild KYC or the ramp UI
A frequent misconception is that integrating a ramp means assembling your own KYC screens or payment forms. With 0Gate you don't. The identity, screening, and payment flows live inside the 0Bit iframe and run against 0Bit's compliance program. You cannot bring your own KYC vendor or brand the KYC documents through 0Gate (unless you are a kyc_trusted partner submitting a kyc_package). If you are a licensed VASP that genuinely owns 100% of your own customer UX and compliance, the future path is the shared-liquidity partner API — not a custom 0Gate front-end.
AML & sanctions screening
AML and sanctions screening are part of 0Bit's compliance program and run on the money leg, not in your app. Screening signals (aml.status and related sub-checks) ride alongside the identity decision; a flow does not proceed on adverse screening even when document verification itself passed. You receive outcomes, never raw screening data.
Chargebacks & fraud
Disputes, fraud signals, and risk decisions on the money leg are 0Bit's responsibility as the regulated operator. Card pay-ins run through a tokenized 3-D Secure flow (device-data collection, tokenize, 3DS challenge, confirm) handled inside the widget and the operator — your server never sees a PAN and never adjudicates a chargeback.
Fiat rails
0Bit operates and reconciles the pay-in and pay-out rails across the supported corridors today:
| Rail | Corridors / methods |
|---|---|
| SEPA | EUR and DKK IBAN pay-ins and payouts. |
| LatAm | Brazil (BR) — PIX, BOLETO; Mexico (MX) — SPEI; Colombia (CO/COP) — PSE; plus tokenized CARD. |
Country-specific pay-out details are collected once and stored against the verified user — e.g. Mexican rfcPf tax id, Colombian colombianDocType (cc/ce), and bank-account fields (accountNumber, branchNumber, bankCode, accountType of CHECKING/SAVINGS, pixKey). 0Bit derives these from the KYC profile and prompts only for genuinely missing fields. Inbound local-rail webhook events are signature-verified by 0Bit and processed idempotently before any session transitions.
On-chain settlement
The crypto leg — sending to a destination wallet on a buy, confirming receipt on a sell — plus on-chain reconciliation, is 0Bit's. Settlement spans these EVM networks:
| Network | Chain id | Explorer |
|---|---|---|
| Ethereum | 1 | etherscan.io |
| Polygon | 137 | polygonscan.com |
| Base | 8453 | basescan.org |
| Arbitrum | 42161 | arbiscan.io |
| Optimism | 10 | optimistic.etherscan.io |
Once mined, the transaction's hash, resolved chain_id, and an explorer_url are exposed on the receipt object (see Settlement & delivery). A partner constraint of target_token and/or target_network on the session locks which asset/chain the user may settle on.
What you own
Your responsibilities are real but narrow. Everything outside this list is 0Bit's.
Your app and your users
You bring the audience and the surrounding product. The customer relationship at your layer is yours; the identity-of-record for the regulated flow is 0Bit's.
Embedding the widget
Mount the iframe (or a flow-locked kit block) and style the page around it. The compliance, payment-method, and KYC screens stay inside the widget. The widget is served from gate.0bit.app (live) / gate-sandbox.0bit.app (sandbox).
Creating sessions
Call POST /v1/gate_sessions from your server with an sk_* key, with amount / currency / return_url bound in (and optionally flow, target_token, target_network, wallet_address, user_reference). This write requires an Idempotency-Key UUID.
Configuring your integration
Set your webhook_url and allowed_domains (the exact-match origin allowlist for the iframe — no wildcards) in Partner Hub, and manage your keys and webhook signing secret there.
Handling webhooks
Receive 0Bit's signed events, verify the Gate-Signature header, treat them as the source of truth for settlement, and keep your handler idempotent (dedupe on the per-delivery event id).
Reacting to outcomes
Show a "processing" state on the browser callback; fulfil/credit your user only when the authoritative webhook arrives.
How the responsibility boundary plays out
The boundary is enforced by the architecture, not just by policy. A checkout crosses the line exactly twice — when you create a session, and when you receive the outcome — and 0Bit owns everything in between.
Notice what the partner server never does: it never runs KYC, never screens a counterparty, never signs an on-chain transaction, and never initiates a bank transfer. Those steps all sit with 0Bit between the two crossings.
The browser callback is UX; the webhook is truth
The widget's onSuccess callback means "the user finished the flow" — not "money has settled." Treat it as a cue to show a friendly processing state. Settlement is confirmed only by the webhook, which 0Bit POSTs to your webhook_url and signs so you can trust it.
Verify the Gate-Signature header (format t=<unix>,v1=<hex>, HMAC-SHA256 over <timestamp>.<rawBody>, 300-second tolerance) against your webhook signing secret (prefix whsec_) before acting on any event. Companion headers X-0bit-Timestamp, X-0bit-Event-Id (your dedupe key), and X-0bit-Event-Type accompany every delivery. The SDKs do the verification for you:
import { GateClient } from "@0bit/gate";
const client = new GateClient({ apiKey: process.env.GATE_KEY });
// rawBody is the unparsed request body; never JSON.parse before verifying.
const event = client.webhooks.constructEvent(
rawBody,
req.headers["gate-signature"], // canonical header — NEVER x-0bit-signature
process.env.WEBHOOK_SECRET,
);
if (event.type === "gate_session.completed") {
// Authoritative: now safe to credit/fulfil the user. Make this idempotent.
}The header is Gate-Signature
The webhook signature header is Gate-Signature — never x-0bit-signature. Settlement events (gate_session.completed, gate_session.created, gate_session.expired, gate_session.cancelled), plus the operational events gate_session.kyc_package_accepted, kyc.required, and partner.quota.warning / partner.quota.exhausted, all arrive with this header. Confirm the signature before treating any payload as real, and keep your handler idempotent so retries are safe.
Compliance can pause a flow, not your code
When KYC is required mid-session (a session created with a verification-gating reference), 0Bit emits a signed kyc.required event rather than letting your server make the call. You react in the UI; the gate itself is enforced inside the operator. This keeps the verification decision — and the liability — entirely on 0Bit's side of the line.
Settlement & delivery
Settlement is the moment the two legs reconcile inside 0Bit. The signal that it happened is the webhook; the durable record is the receipt.
The completion signal
gate_session.completed fires when the buy/sell/swap linked to the session succeeds. Its payload is the full GateSession plus data.tx_refid — the refid of the underlying transaction. Use tx_refid to join back to your order record and to deep-link to the receipt. The session's own status moves through open → one of the terminal states completed, expired (lazily set on the first read past expires_at, default 24h), or cancelled.
The receipt object
Each completed transaction has a denormalised receipt you can render without follow-up calls. Its settlement-relevant fields:
| Field | Type | Notes |
|---|---|---|
refid | string | Joins to tx_refid from the completed event. |
action | BUY | SELL | Direction of the fiat/crypto legs. |
status | string | Settlement status of the underlying transaction. |
token / token_amt / network | string | The crypto leg. |
currency / fiat_amt / exchange_rate / total_fees / total_pay_or_receive | string | The fiat leg. |
payment_method | string | The rail/method used. |
user_address | string? | BUY only — the wallet that received crypto. |
bank | object? | SELL only — account_name, account_number, routing_number, bank_name, additional_details. |
local_rail_transaction_id | string? | Present when the tx settled through a LatAm local rail — the rail's transaction id. (facilita_transaction_id is a deprecated alias carrying the same value.) |
blockchain | object? | Present once mined — hash, chain_id, explorer_url. |
permalink_url / permalink_token | string | A signed, stable URL that re-renders the receipt without auth. |
On-chain proof
For an on-chain leg, the receipt's blockchain.hash plus the network's explorer (the chain-id table in On-chain settlement) gives a verifiable explorer_url. This is 0Bit's settlement proof surfaced to you — your server never derived it and never signed the transaction that produced it.
The signed permalink
The permalink_url carries a permalink_token that is a deterministic HMAC-SHA256 of the refid (keyed by a 0Bit-held server secret), letting any historical or new transaction produce a stable, no-auth link. The token is verified in constant time. Because it is 0Bit-keyed, you cannot forge or recompute it — you receive it on the receipt and link to it as-is.
The boundary holds on every tier
This split is not a starter-tier limitation that erodes as you scale. It holds for every flow because every flow derives the end user from a 0Bit-run session, and 0Bit is the regulated operator that handles KYC, AML, and settlement. Your partner key authenticates your org; it never asserts who the customer is or that they were verified, and it never moves user funds directly — only a 0Bit session carries the verified end user that authorizes a money-moving flow.
The credentials themselves reflect the boundary. Publishable keys (pk_*) are browser-safe and can do exactly one thing: exchange for a short-lived embed token via POST /v1/embed/bootstrap. Secret keys (sk_*) are server-only and create or cancel sessions. The embed token (a 1h HS256 JWT sent as X-Embed-Token) is what stamps verified partner + session context on every downstream call — neither key, nor a leaked publishable key, lets the browser move money or alter a bound amount. See Authentication for the full credential and embed-handshake reference.
One spine, more products coming
0Gate is the flagship and the only partner-ready product today. It draws on 0Bit's shared liquidity spine; additional products build on the same non-custodial, compliance-owned foundation but are not usable yet. Whatever ships next inherits this same boundary: 0Bit owns identity, screening, rails, and settlement; you own your app and your webhooks.
See also
0Gate
The live product — embed buy, sell, and swap with server sessions and signed webhooks.
Authentication
Publishable vs secret keys, the embed bootstrap handshake, and webhook signing.
Sessions
The unit of a single checkout, its lifecycle, and where the responsibility line is crossed.
Webhooks
Why webhooks are the source of truth for settlement, and how to verify them.