0Bit Documentation

Lock an off-ramp flow

Build a hosted 0Gate sell flow that keeps payout handling, terminal state, and support recovery server-owned.

Use an off-ramp flow when the customer wants to sell crypto and receive a payout through the hosted 0Gate experience. Keep the user in 0Gate for the hosted flow, and keep final state in your backend.

Keep payout details partner-safe

This guide covers the partner workflow: create the intent, open the hosted flow, and close your record from verified backend state. Do not rely on browser callbacks for payout completion or expose provider, treasury, or settlement-operation details in your customer UI.

Flow boundary

BoundaryOwned byWhat to store
Sell intentYour appAccount id, requested source asset, fiat currency, amount intent, and your reference id.
Hosted flow0GateCustomer-facing sell journey, compliance checks, quote confirmation, and payout UX.
Backend event0Gate to youSigned terminal event with enough reference data to close your own record.
Support recordYouAttempt timeline, event ids, current state, and retry/reconciliation notes.

Implementation shape

  1. Create a sell intent in your system.
  2. Create a server-side 0Gate session with the flow locked to off-ramp.
  3. Store the session id and show the hosted sell block or redirect.
  4. Move the browser to processing after hosted success.
  5. Close the sell intent only from verified backend state.
async function startOffRamp(input: OffRampIntent) {
  const intent = await sellIntents.create({
    accountId: input.accountId,
    sourceAsset: input.sourceAsset,
    fiatCurrency: input.fiatCurrency,
    status: 'pending_session',
  });

  const session = await gateSessions.createForAttempt({
    attemptId: intent.id,
    flow: 'off_ramp',
    userReference: intent.id,
    idempotencyKey: `off-ramp:${intent.id}`,
  });

  await sellIntents.attachGateSession(intent.id, session.id);
  return { intentId: intent.id, clientSecret: session.clientSecret };
}

State model

StateMeaningPartner action
pending_sessionSell intent exists, hosted session is not attached yet.Keep retry safe with one idempotency key.
requires_actionHosted off-ramp flow is open.Let the user continue in 0Gate.
processingHosted UX finished, backend terminal state is pending.Show processing and wait for trusted state.
fulfilledVerified terminal state closed the sell intent.Update balances, receipts, or support timeline once.
failedThe sell flow failed.Keep the reason user-safe and offer retry/support.
expiredThe session timed out.Let the user start a new attempt.
cancelledThe customer or server cancelled.Preserve the audit trail; do not reuse the session.

Payout-safe UX

ScreenRecommended copyAvoid
Before hosted flowConfirm amount, asset, and destination summary if you collect them.Claims about exact rail timing unless approved.
After callback“We are confirming your sell order.”“Paid out” before backend confirmation.
Failure“This flow could not be completed. Try again or contact support.”Provider errors, compliance rule names, treasury balances.
SupportShow attempt id, status, timestamp, and next action.Full webhook payloads or internal settlement notes.

Webhook and reconciliation

For sell flows, be stricter about duplicate handling because customers may refresh or contact support while payout state is still processing.

async function closeSellIntent(event: GateSessionEvent) {
  const intent = await sellIntents.findByGateSession(event.data.id);
  if (!intent) return;

  await webhookEvents.insertOnceOrReturn(event.id);

  if (event.type === 'gate_session.completed') {
    await sellIntents.fulfillOnce(intent.id, {
      sourceEventId: event.id,
      completedAt: event.createdAt,
    });
  }
}

Production checklist

  • Lock off_ramp on the server and keep browser config secondary.
  • Do not expose rail, provider, treasury, or compliance internals in public UI.
  • Make duplicate webhooks no-ops.
  • Give support a trusted status view backed by your database.
  • Treat reconciliation as recovery, not a polling-first architecture.

On this page