Developers
Core Concepts

KYC & verification

How identity verification works on 0Gate — KYC runs on the end user inside the widget, before money moves, only when required for the amount or corridor. 0Bit owns AML and sanctions screening. Partners never build or see raw KYC; enterprise partners can pass a trusted verification through with kyc_package.

On 0Gate, identity verification runs on the end user, inside the widget, and is owned end to end by 0Bit. When a buy, sell, or swap requires it, the user completes KYC in the 0Bit iframe — against 0Bit's verification providers and compliance program — before any money moves. The partner never builds KYC screens, never collects or stores identity documents, and never sees raw verification data. You bring the user and the page; 0Bit runs identity, AML, and sanctions. This page is the mental model for that; the credential and webhook mechanics live in Authentication.

This is the deliberate consequence of the non-custodial, compliance-owned model: because 0Bit is the operator of record for the regulated activity, 0Bit is also responsible for knowing the customer. KYC isn't a feature you wire up — it's machinery that already sits behind every session.

Verification happens in the widget, before money moves

A GateSession is created on your server with the amount and currency bound in, then mounted in the browser. When the user runs the flow, the widget decides whether this particular checkout needs verification — based on the user, the amount, and the corridor (the fiat/crypto pair and the rails involved). If it does, the user completes KYC inside the iframe before the fiat or on-chain leg executes. Verification gates settlement; it is never an afterthought.

Three things follow from "in the widget":

  • It runs on the end user, not on you. The person being verified is the user transacting in the iframe. Your partner credentials authenticate your organization; they never assert who the customer is. Only a 0Bit session carries an identity.
  • It only happens when required. Many checkouts need no new verification — a returning user, a small amount, an already-cleared corridor. KYC is conditional, applied per the risk profile of that session, not a fixed step on every transaction.
  • You never see the raw KYC. Documents, selfies, liveness checks, and provider responses stay inside 0Bit. You receive events about verification (see below) and the final settlement outcome — never the underlying identity data.

0Bit owns the verification providers

Identity verification is performed by 0Bit's verification partner running inside the 0Bit widget. You cannot bring your own KYC vendor, brand the identity screens, or change the document set through standard 0Gate. That boundary is the product: it's what lets you ship a compliant ramp without becoming a regulated money transmitter yourself.

When verification happens

Verification is not a fixed step on every transaction — the widget evaluates each checkout against the user, the amount, and the corridor and only prompts when the risk profile demands it. Concretely, KYC tends to be triggered when:

  • The user has no cleared verification yet for this 0Bit identity — first transaction, or a profile whose status is not yet Approved.
  • The amount or corridor crosses a threshold where the rail (SEPA for EUR/DKK IBAN, local rails for LatAm corridors) requires identity, address, and — for some corridors — a tax/document number before a payout or pay-in can clear.
  • A re-verification is needed because a prior verification expired, was rejected, or the user is transacting in a corridor that needs fields they haven't supplied yet.

A returning, already-cleared user transacting under the relevant threshold typically sees no verification prompt at all — the session goes straight to settlement. Because the decision is made server-side per session, you cannot predict it from the create call; treat the kyc.required event (below) as the authoritative signal that this checkout entered a verification step.

Verification gates settlement, not session creation

Creating a session never triggers KYC — POST /v1/gate_sessions only binds the amount, currency, and corridor. Verification is evaluated when the user runs the flow inside the iframe, and it sits between the pay-in/pay-out legs. A session can be created, sit idle, and expire (default 24h) without any KYC ever being requested.

Where KYC sits in a checkout

A checkout crosses the partner boundary exactly twice — when your server creates the session, and when 0Bit reports the outcome by webhook. Verification lives entirely inside the iframe, between those two crossings.

Notice what the partner server never does: it never renders a KYC screen, never receives a document, and never makes a verification decision. It creates a session and reacts to signed events.

What the user goes through (conceptually)

From the user's side, verification is a short, in-iframe step — the same shape as any modern KYC flow, run by 0Bit's provider:

  • Identity details and a document. A government ID (passport, ID card, or driver's licence), captured or uploaded inside the widget.
  • A liveness / selfie check. Confirming the person matches the document.
  • Provider processing. The verification provider evaluates the submission; 0Bit layers its own AML and sanctions screening on top.
  • Outcome. Once cleared, the session continues to settlement. If verification fails or is abandoned, the session ends in a terminal state (failed / cancelled) and you learn the outcome by webhook.

All of this is rendered and handled by 0Bit. You don't design these screens, choose the document set, or receive the artifacts — you only see the verification events and the eventual settlement event.

The verification lifecycle inside 0Bit

Under the hood, 0Bit opens a provider verification session for the user (the document + liveness + AML checks above), and the provider reports its decision back to 0Bit asynchronously. The decision carries a status — the value that matters is Approved. Until a user reaches Approved, the widget keeps them in the verification step and the session cannot settle. The provider's decision also contains sub-checks 0Bit evaluates internally — face-match, liveness, proof-of-address, and an AML result — none of which are exposed to partners.

This decision is what drives the kyc.required event you see: when a verification reports any status other than Approved for a session that is mid-flow, 0Bit signals that the checkout is still pending identity. The first time a user is verified establishes their cleared identity; subsequent sessions for that user reuse it (subject to amount/corridor re-checks), which is why most returning checkouts never prompt again.

Corridor-specific identity fields

For some payout corridors the rail needs more than a passport scan. After a user is Approved, settling certain LatAm corridors can require fields the document alone doesn't carry — a national tax/document number, a structured address, and sometimes a bank-account detail. 0Bit collects and holds these as part of the verified profile; the partner never sees or supplies them. Examples drawn from the live rails:

CorridorExtra identity field 0Bit may requireNotes
Mexico (MX)rfcPf (tax ID)Required for the Mexican payout rail
Colombia (CO)colombianDocType (cc / ce)Required to route the Colombian payout
Brazil (BR)Structured address + PIX keyAddress parsed from the verified profile

These are listed only to make concrete why verification is corridor-aware — they are entirely owned and collected by 0Bit. As a partner you never pass, store, or screen any of them.

The kyc.required event

When a session needs the user to verify, 0Bit emits a kyc.required webhook to your endpoint. It is an operational signal, not a settlement event: it tells you that this checkout has entered a verification step inside the widget. Most integrations don't need to take any action on it — the user is already being prompted in the iframe — but it is useful for analytics, support context ("the user is mid-verification"), and for surfacing an accurate status in your own UI.

Like every 0Gate webhook, kyc.required arrives with the canonical Gate-Signature header (t=<unix>,v1=<hex>, HMAC-SHA256 over <timestamp>.<rawBody>, 300-second tolerance) and must be verified against your whsec_ signing secret before you trust it. The SDKs do this for you:

Node — verify before you trust
import { GateClient } from "@0bit/gate";

const client = new GateClient({ apiKey: process.env.GATE_KEY });

const event = client.webhooks.constructEvent(
  rawBody,
  req.headers["gate-signature"], // canonical header — never x-0bit-signature
  process.env.WEBHOOK_SECRET,
);

if (event.type === "kyc.required") {
  // The user is being asked to verify inside the widget.
  // Optional: reflect "verifying" in your own UI. No action is required.
}

Event envelope and data shape

kyc.required is delivered as the standard 0Gate event envelope — a JSON body of { id, type, created_at, data } — alongside these companion headers, all of which the SDK reads for you:

HeaderValue
Gate-Signaturet=<unix>,v1=<hmac-sha256-hex> over <timestamp>.<rawBody>
X-0bit-TimestampUnix seconds the signature was generated
X-0bit-Event-IdThe event id (UUID) — your dedupe key
X-0bit-Event-Typekyc.required
User-Agent0bit-webhooks/1.0

The data object for kyc.required carries the correlation fields 0Bit has at the time it blocks the session:

FieldTypeDescription
gate_session_idstringThe session that is now pending verification — join this to your order.
kyc_statusstringThe non-Approved verification status that triggered the block (e.g. the provider's pending/declined value).
user_referencestringThe opaque user identifier tied to the verification, for partner-side correlation.

kyc.required is emitted, not specced

kyc.required is a real, shipped event but is not listed in the OpenAPI Event schema (which documents gate_session.created, .cancelled, .expired, .completed, and .kyc_package_accepted). Don't rely on the spec's enum to enumerate the events you can receive — register a handler that tolerates kyc.required (and the quota events) even though they aren't in the spec's list. The authoritative event union is the one the delivery engine emits.

When kyc.required actually fires

It is emitted only when 0Bit has a session to attach the signal to — internally, when a verification reports a status other than Approved and that verification is linked to a live gate session. It is fire-and-forget on 0Bit's side: a failure to enqueue it never blocks or fails the user's flow, so absence of the event is not a guarantee that verification wasn't requested. Treat it as a best-effort heads-up, and treat the terminal session event as truth.

kyc.required is not a failure, and not settlement

kyc.required does not mean the checkout failed — it means verification is in progress inside the widget. Don't treat it as terminal. The authoritative outcome is still gate_session.completed (or gate_session.failed), confirmed by webhook. Keep every handler idempotent so retries are safe — webhook retries reuse the same event id, so dedupe on X-0bit-Event-Id.

0Bit owns AML & sanctions screening too

KYC is identity. AML and sanctions screening is the separate, ongoing compliance layer that sits alongside it — counterparty and transaction screening against 0Bit's compliance program — and 0Bit owns it on every tier. It applies even where no fresh KYC prompt is shown to the user (for example, screening a destination address or a counterparty on a transaction). You never run sanctions lists, score risk, or make an AML decision; that machinery is part of the same regulated stack that owns the rails and on-chain settlement.

What AML screening covers

Screening runs as part of the same verification decision and again at settlement time — it is not a one-time check folded into onboarding. In practice it spans:

  • Identity-time AML. The provider's decision includes an AML result evaluated alongside face-match, liveness, and proof-of-address; 0Bit gates approval on it.
  • Transaction and counterparty screening. Destination wallet addresses and payout counterparties are screened on the money/on-chain leg, independent of whether the user was freshly prompted for KYC.
  • Ongoing posture. Because 0Bit is the operator of record, screening obligations persist across a user's transactions, not just at first verification.

None of these surface to the partner as data. You may observe their effect — a session that ends failed, or one that emits kyc.required and never completes — but never the AML scores, lists, or decisions themselves.

Responsibility matrix

ResponsibilityOwner
Identity verification (KYC) on the end user0Bit — inside the widget, via providers
AML & sanctions / counterparty screening0Bit — on every tier
Chargeback & fraud handling on the money leg0Bit
Collecting / storing / screening identity documents0Bit — partners never see raw KYC
Embedding the widget and reacting to webhooksYou (the partner)

Partners are not responsible for KYC or AML

You are not the regulated entity for the ramp. You do not file KYC, run sanctions screening, or own AML obligations for a 0Gate transaction — 0Bit does. Your job is to embed the widget, create sessions server-side, and act on signed webhooks. This is what keeps your integration narrow and keeps you out of money-transmitter territory.

Advanced: passing a trusted verification with kyc_package

Some enterprise partners are themselves regulated and already KYC their users to a standard 0Bit can rely on. For those partners — and only under a specific contract — 0Gate supports passing a trusted verification through so the user doesn't have to re-verify inside the widget. This is the kyc_package capability.

This is an advanced, contract-gated path, not the default. It is enabled per partner (a kyc_trusted flag set by 0Bit, never self-serve), and it does not remove 0Bit's screening obligations — AML and sanctions checks still run.

The kyc_trusted gate (contract-only)

kyc_package is accepted only when the partner record carries kyc_trusted: true. That flag defaults to false for every partner and is not partner-settable — there is no portal toggle and no API for you to flip it. 0Bit sets it out-of-band after a contract sign-off (legal needs to know whose KYC provider is being trusted, in which jurisdictions, with what retention), via the internal admin endpoint PATCH /admin/partners/:id/kyc-trusted.

If you send a kyc_package while kyc_trusted is still false, the create call is rejected, not silently ignored:

403 when not contractually trusted
POST /v1/gate_sessions
{ "amount": "250.00", "currency": "EUR", "return_url": "...", "kyc_package": { "provider": "<your-kyc-vendor>", ... } }

HTTP/1.1 403 Forbidden
{
  "message": "kyc_package may only be supplied by partners with kyc_trusted=true (requires contract sign-off)",
  "error": "Forbidden",
  "code": "kyc_package_not_trusted"
}

The 403 is deliberate: a hard error means you notice your client is sending a trusted KYC payload before you have legal sign-off, rather than discovering it was being dropped.

How acceptance flows

When kyc_trusted: true and you include kyc_package on POST /v1/gate_sessions:

  • The session is created with kyc_pre_verified: true — a derived boolean computed from kyc_package + kyc_trusted at create time. This boolean is echoed in the session object and in the embed bootstrap response (INIT_CONFIG), so the iframe can route around its own KYC step.
  • 0Bit emits a gate_session.kyc_package_accepted webhook in addition to the usual gate_session.created.
  • The raw kyc_package body is never echoed — not in the session object, not in any webhook, not in any API response. Only the redacted acceptance envelope below is sent.

If the partner is not entitled, or the package isn't accepted, the flow falls back to standard in-widget KYC — the session still completes safely, the user just verifies normally.

The kyc_package input

FieldTypeRequiredDescription
providerstringRecommendedYour KYC vendor name (e.g. <your-kyc-vendor>), surfaced in the acceptance audit envelope. Null if unspecified.
(other fields)objectPer contractShape is opaque to the API (additionalProperties: true). The legal contract carries the required fields and provider expectations.

It is sent inside the POST /v1/gate_sessions body alongside amount, currency, and return_url. Two sibling enterprise inputs — wallet_address (≤128 chars) and user_reference (≤128 chars) — are accepted from any partner because they are non-PII convenience identifiers; only kyc_package is gated by kyc_trusted.

The gate_session.kyc_package_accepted event

Delivered once per session at create time, and only when a kyc_trusted partner submitted a kyc_package. The payload is a deliberately minimal, redacted KycPackageAcceptance envelope — correlation and audit only, no PII:

FieldTypeDescription
session_idstringThe session the package was accepted on.
partner_idstringYour partner id.
modestringtest or live.
providerstring | nullFrom your submitted kyc_package.provider. Null if you didn't supply one.
accepted_atstring (date-time)When acceptance was recorded.
user_referencestring | nullEchoed from the session for partner-side correlation.

Partners who need the full payload keep their own copy server-side — you sent it, so 0Bit doesn't hand it back.

kyc_package is contract-gated, not a default integration step

Do not build against kyc_package unless 0Bit has explicitly enabled it for your organization under a trusted-KYC arrangement (kyc_trusted: true). Standard partners ignore it entirely and let verification run in the widget — which is the right, lower-liability default. Passing a trusted KYC package means you are asserting a verification, which only makes sense if you are a regulated party who has agreed to that responsibility with 0Bit.

See also

On this page