Developers
Core Concepts

Buy, sell & swap

The three flows 0Gate runs — buy (fiat to crypto, on_ramp), sell (crypto to fiat, off_ramp), and swap (crypto to crypto) — what each is for, the high-level path every one follows, and how a session's flow parameter selects between them.

0Gate moves value across exactly one boundary in three directions. Buy takes fiat and delivers crypto to a wallet. Sell takes crypto and pays fiat to a bank or payout method. Swap takes one crypto and returns another. They are three faces of the same machine: the same session object, the same embed handshake, the same KYC and screening, the same signed webhook closing the loop. What changes between them is only the direction — what the user provides, and where the value lands.

This page is the mental model for those three flows. You do not build any of them screen-by-screen; you create a session, hand the widget a client_secret, and 0Bit runs the conversion and settles to the destination. The how-to — installing an SDK, mounting the widget, picking an integration shape — lives in 0Gate quickstart and Choose your integration. Here we explain what each flow is and the path it follows.

One session, one direction

A flow is not a different product or a different API. It is a property of the session — the flow field, set to on_ramp, off_ramp, or swap. Everything else about creating, mounting, and settling a session is identical across the three.

The three flows at a glance

Every flow is fiat ↔ crypto (or crypto ↔ crypto) executed by 0Bit between two endpoints. The user supplies one side; 0Bit converts and settles the other side to the destination.

Flowflow valueDirectionWhat the user providesWhere value lands
Buyon_rampFiat → cryptoFiat payment (card / bank rail)Destination wallet (crypto)
Selloff_rampCrypto → fiatCrypto sent to 0BitDestination bank / payout method (fiat)
SwapswapCrypto → cryptoCrypto sent to 0BitDestination wallet (the target crypto)

In all three, 0Bit is the regulated operator in the middle: it runs KYC and AML/sanctions screening, takes the inbound leg, performs the conversion, and settles the outbound leg to the destination — non-custodially for you, the partner. You never touch the money leg. See Custody & compliance for exactly where that line sits.

The fields that shape any flow

Whatever the direction, a flow's behaviour is set by the same handful of fields on the session you create with POST /v1/gate_sessions. These are the levers — each is described in full in Sessions and the API reference, but the ones that change what a flow does are:

FieldTypeRequiredWhat it does to the flow
amountstringyesDecimal string, pattern ^[0-9]+(\.[0-9]{1,8})?$, max 8 fractional digits, > 0. Server-bound — the buy/sell/swap leg must match exactly or it is rejected.
currencystringyesISO 4217 three-letter code (pattern ^[A-Za-z]{3}$). The fiat side for buy/sell; the quote currency for swap.
return_urlstring (uri)yesWhere the iframe sends the user after a successful flow. HTTPS only (loopback in dev); origin must be in partner.allowed_domains.
flowenumnoon_ramp | off_ramp | swap. Locks the direction. Omit for the open widget where the user picks via tabs (see Selecting a flow).
target_tokenstringnoConstrains the crypto side (e.g. USDC); pattern ^[A-Za-z0-9]{2,12}$.
target_networkstringnoConstrains the chain (e.g. ETHEREUM, POLYGON); pattern ^[A-Za-z0-9_-]{2,30}$.
wallet_addressstringnoPre-filled destination wallet (max 128 chars). Validated against the resolved network at flow time, not at session-create.
cancel_urlstring (uri)noWhere to send the user on cancel.
user_referencestringnoOpaque partner-side id (max 128 chars). Echoed in webhook payloads for correlation.
metadataobjectnoOpaque per-session notes, returned verbatim; not used for business logic.

target_token / target_network are partner constraints, not requirements: set them to pin the user to one asset/chain, or leave them off to let the user choose inside the widget from 0Bit's live capability set.

Buy — fiat to crypto (on_ramp)

Buy is an on-ramp: the user pays fiat and crypto is delivered to a wallet. It is the flow you reach for when your product needs to get someone into crypto — funding a wallet, topping up an in-app balance, acquiring a token to use elsewhere. The user brings money (a card or bank payment in their local currency); 0Bit converts it and settles the purchased asset to the destination wallet.

The high-level path:

  1. The user picks an amount (or you bind one in at session create).
  2. 0Bit runs KYC inside the widget if the user is not already verified for this tier and amount.
  3. The user pays via a supported fiat rail — the payment screens are 0Bit's, served inside the iframe.
  4. 0Bit converts the fiat to the target crypto and settles it on-chain to the destination wallet.
  5. A signed gate_session.completed webhook tells your server the buy is done.

Buy is one of 0Gate's two core, production flows. (Its coverage, like every flow's, is operator-governed — see Coverage & maturity.)

Direction & where value lands

AspectBuy
flow valueon_ramp
Inbound leg (user provides)Fiat payment — currency (e.g. EUR), amount
Outbound leg (value lands)Crypto, on-chain, to the destination wallet
Crypto side controlled bytarget_token / target_network (or user choice in the widget); wallet_address for the destination
Settlement signalgate_session.completed webhook, with data.tx_refid

What you bind, what the user supplies

The fiat side (amount, currency) is server-bound when you create the session — the user cannot tamper with the locked value. The crypto side is either pinned by you (target_token/target_network/wallet_address) or left to the user to choose in the widget. The payment method (card vs bank rail) and the KYC step are 0Bit's screens, rendered inside the iframe; you do not collect card data or run identity checks yourself. See Custody & compliance.

Sell — crypto to fiat (off_ramp)

Sell is an off-ramp: the user sends crypto and fiat is paid out to a bank or payout method. This is the flow for getting value out of crypto — cashing out a balance, paying a user in their local currency, settling earnings to a bank account. The user brings crypto; 0Bit converts it and pays the proceeds to the destination fiat payout method.

The high-level path:

  1. The user picks an amount to sell (or you bind it in at create).
  2. 0Bit runs KYC inside the widget where required.
  3. The user sends the crypto to 0Bit (the widget shows the address / on-chain instructions).
  4. 0Bit confirms receipt, converts to the chosen fiat currency, and settles to the destination bank / payout method.
  5. A signed gate_session.completed webhook confirms the payout leg.

Sell is the other core production flow.

Direction & where value lands

AspectSell
flow valueoff_ramp
Inbound leg (user provides)Crypto sent on-chain to a 0Bit-shown address
Outbound leg (value lands)Fiat, to the destination bank / payout method
Fiat side controlled bycurrency (the payout currency) + amount
Settlement signalgate_session.completed webhook (with data.tx_refid); PAYMENT_SUCCESS may carry amount / currency

Why "success" is server-confirmed, not browser-confirmed

The inbound leg is an on-chain deposit and the outbound leg is a real bank payout — neither completes the instant the user finishes the widget. The iframe's PAYMENT_SUCCESS postMessage (and the SDK's onSuccess callback) carries { txId, sessionId?, amount?, currency? } and means the user finished their part, not the payout settled. Treat the gate_session.completed webhook as the source of truth for sell — it fires only when the linked intent succeeds.

Buy and sell are the core

On-ramp and off-ramp are the mature, day-one flows of 0Gate. If your integration is about getting users into or out of crypto, these are the two you build on.

Swap — crypto to crypto (swap)

Swap is crypto-to-crypto: the user sends one asset and receives another at the destination wallet. No fiat rail is involved on either side — the user brings crypto, 0Bit converts it across, and settles the target asset to a wallet. It is useful when a user already holds crypto and needs a different asset without leaving your surface.

The high-level path mirrors a sell on the inbound side and a buy on the outbound side:

  1. The user picks the source asset, the target asset, and an amount.
  2. 0Bit runs KYC inside the widget where required.
  3. The user sends the source crypto to 0Bit.
  4. 0Bit converts to the target asset and settles it on-chain to the destination wallet.
  5. A signed gate_session.completed webhook confirms the swap.

Direction & where value lands

AspectSwap
flow valueswap
Inbound leg (user provides)Source crypto sent on-chain to a 0Bit-shown address
Outbound leg (value lands)Target crypto, on-chain, to the destination wallet
Target asset controlled bytarget_token / target_network (or user choice); wallet_address for the destination
Settlement signalgate_session.completed webhook, with data.tx_refid

Swap is part of the widget — don't over-promise it

Swap ships as a flow in the 0Gate widget alongside buy and sell, but it is less mature than the two fiat ramps. Treat its asset coverage, network support, and limits as live, operator-governed values surfaced by the widget — not a fixed catalogue to advertise to your users. If your integration depends on swap availability for a specific pair, confirm it against the live widget rather than documenting guarantees.

Selecting a flow

There are three independent places the direction can be expressed: the session's flow field (the authority), the widget's tab strip, and the browser SDK's kit-block factories. They must agree — and where they disagree, the session wins.

The flow field on the session

The flow is chosen by the session's flow field, set when you create the session on your server with your sk_ secret key. It takes one of three values — on_ramp, off_ramp, or swap — and it locks the widget to that single direction.

curl -X POST https://gate-api.0bit.app/v1/gate_sessions \
  -H "Authorization: Bearer $GATE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "amount": "100.00",
    "currency": "EUR",
    "flow": "on_ramp",
    "return_url": "https://app.example.com/return"
  }'

If you omit flow, the session is not locked to a direction and the widget renders with tabs — the user chooses buy, sell, or swap themselves. (On the session object the unset value is null; the API treats null and omitted identically.) Setting flow is how you ship a single-purpose surface (a "Buy crypto" button that only ever buys) instead of the full three-tab ramp.

flow valueWhat the widget shows
(omitted / null)The full widget with buy / sell / swap tabs — the user picks.
on_rampBuy only — fiat to crypto, no tab strip.
off_rampSell only — crypto to fiat, no tab strip.
swapSwap only — crypto to crypto, no tab strip.

How the lock reaches the iframe

The flow you set on the session is carried all the way to the widget, so the iframe can hide the tab strip before the user sees it:

  1. POST /v1/gate_sessions stores flow on the GateSession.
  2. The browser bootstraps with POST /v1/embed/bootstrap (publishable key + clientSecret); the EmbedBootstrapResponse echoes back flow alongside amount, currency, target_token, target_network, and return_url.
  3. The SDK forwards flow to the iframe in the INIT_CONFIG postMessage. When it is set, the iframe-app hides the buy/sell/swap tab strip and forces the chosen view.

Because the value originates from a session created with your sk_ key, the direction is bound server-side — the browser cannot switch a "buy" session into a "sell".

Kit-block factories (browser SDK)

The browser SDK @0bit/gate/browser exposes the same choice ergonomically. You can mount the full widget and let the user pick a tab, or use a flow-locked kit block:

import { createGateOnRamp } from '@0bit/gate/browser';

const ramp = createGateOnRamp({
  publishableKey: 'pk_test_...',
  clientSecret: session.client_secret,
});
await ramp.mount('#buy-crypto');

Each factory is a thin wrapper that returns a GateRamp with flow pre-set:

FactorySets flowReact component
createGateOnRamp(config)on_ramp<GateOnRamp />
createGateOffRamp(config)off_ramp<GateOffRamp />
createGateSwap(config)swap<GateSwap />

A kit block is the same hosted screens as the full widget with the direction pinned and the tab UI hidden. The React package (@0bit/gate/react) ships the matching <GateOnRamp>, <GateOffRamp>, and <GateSwap> wrappers around <RampCheckout>. See Choose your integration for when to lock a flow versus show tabs.

The session is the authority — the factory is a fallback

The factory's flow is, per the SDK source, "a local assertion / fallback for partners not yet using sessions." The canonical place to set the direction is the session created on your server. Keep the kit-block factory and the session's flow in agreement; if they disagree, the session wins. Because flow is bound at session-create with your sk_ key, the browser can't override it.

Coverage & maturity

Be honest with your own users about what each flow guarantees today. The three flows are not equally mature: buy and sell are the mature, day-one ramps, while swap is the newer widget flow. Coverage inside each — fiat currencies, payment rails, networks, and assets — is operator-governed and entitlement-gated, surfaced by the live widget at runtime. Read it from that live capability set rather than hard-coding a coverage matrix.

Capability is signalled, not assumed

The platform tells you when a flow can't run rather than failing silently. The EmbedBootstrapResponse carries available and unavailable_reason; when available is false, the SDK forwards it through INIT_CONFIG and the iframe renders a neutral "service unavailable" overlay instead of the checkout UI. Stable reason strings include quota_exhausted and partner_suspended. On the server side, partner.quota.warning and partner.quota.exhausted webhooks warn you before and when entitlements run out. Treat these signals — not a documented coverage table — as the truth about what is available.

0Gate is the partner-ready product

Buy, sell, and swap are 0Gate (fiat↔crypto). 0Pools is a gated, approved-partner liquidity API (early access, provisioned per partner by 0Bit) — not the default public path; most integrations should use 0Gate.

One path, three directions

Strip away the direction and all three flows are the same shape: the user picks an amount, KYC runs in the widget if needed, value moves in on one side, 0Bit converts, and value settles out to the destination — confirmed by a signed webhook.

The settlement signal is identical

In every case the loop closes the same way: a gate_session.completed webhook, signed with the Gate-Signature header (t=<unix>,v1=<hex>, HMAC-SHA256 over <timestamp>.<rawBody>, 300-second tolerance), verified against your whsec_ secret. The completed event's payload adds data.tx_refid — the refid of the underlying crypto transaction — so you can join the settlement back to your order record. Companion headers on every webhook delivery are X-0bit-Timestamp, X-0bit-Event-Id (your dedupe key — retries reuse it), X-0bit-Event-Type, and User-Agent: 0bit-webhooks/1.0. (Never look for an x-0bit-signature header; it does not exist on the wire.)

The browser onSuccess callback / PAYMENT_SUCCESS postMessage means "the user finished," not "value settled" — wait for the webhook.

Events that fire across the flows

The same event vocabulary covers all three directions. Beyond gate_session.completed, your endpoint may receive:

EventWhen
gate_session.createdPOST /v1/gate_sessions succeeds.
gate_session.completedA buy/sell/swap linked to the session succeeds (adds data.tx_refid).
gate_session.expiredFired lazily on the first read past expires_at (default 24h).
gate_session.cancelledPOST /v1/gate_sessions/:id/cancel succeeds.
gate_session.failedA linked intent failed. (Emitted by the backend; not in the OpenAPI spec.)
gate_session.kyc_package_acceptedA kyc_trusted partner's pre-verified KYC was accepted (redacted, no PII).
kyc.requiredThe user must complete KYC before the flow can proceed.
partner.quota.warning / partner.quota.exhaustedEntitlement/quota thresholds — relevant to coverage (see above).

Build your handler to ignore unknown types so undocumented events don't break it.

Where KYC fits

KYC is not a fourth flow — it runs inside whichever flow needs it. 0Bit's verification partner verifies the user in the widget, and your server may see a kyc.required event. On contract-gated tiers, a trusted-KYC pass-through (kyc_package, accepted only when partner.kyc_trusted is true) sets kyc_pre_verified: true on the session and fires a redacted gate_session.kyc_package_accepted event, letting an already-verified user skip re-verification. The raw kyc_package is never echoed in any API response or webhook. Identity is always owned by 0Bit — see Custody & compliance.

See also

On this page