Developers

Sample projects

Runnable 0Gate-shaped server, browser, React, and webhook examples based on the audited SDK and OpenAPI contract.

These samples show the production shape of a 0Gate integration without exposing internal systems or unsupported product claims. They use fake ids, fake keys, and partner-owned URLs.

Run these against your own sandbox account

The SDK unit tests pass locally, but these app examples still need your sandbox keys, allowed origins, webhook URL, and partner configuration before they can run end-to-end.

Architecture

The browser callback is not settlement truth. Treat it as a UI signal and keep the order in a pending state until the signed webhook is processed.

Environment

.env
OBIT_GATE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OBIT_GATE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OBIT_GATE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OBIT_GATE_API_ORIGIN=https://gate-api-sandbox.0bit.app
PUBLIC_BASE_URL=https://partner.example

The SDK API value is the origin only. Raw HTTP calls use the OpenAPI base URL https://gate-api-sandbox.0bit.app/v1.

Express server

server.ts
import express from 'express';
import { GateClient, WebhookSignatureError } from '@0bit/gate';

const app = express();
const gate = new GateClient({
  apiKey: process.env.OBIT_GATE_SECRET_KEY!,
  baseUrl: process.env.OBIT_GATE_API_ORIGIN ?? 'https://gate-api-sandbox.0bit.app',
});

const orders = new Map<
  string,
  {
    status: 'draft' | 'pending' | 'paid' | 'cancelled' | 'expired';
    gateSessionId?: string;
    lastEventId?: string;
  }
>();

app.post('/api/0gate/session', express.json(), async (req, res) => {
  const orderId = String(req.body.orderId);
  const order = orders.get(orderId) ?? { status: 'draft' as const };

  const session = await gate.sessions.create(
    {
      amount: '100.00',
      currency: 'EUR',
      return_url: `${process.env.PUBLIC_BASE_URL}/checkout/${orderId}/complete`,
      cancel_url: `${process.env.PUBLIC_BASE_URL}/checkout/${orderId}/cancel`,
      metadata: { order_id: orderId },
    },
    { idempotencyKey: `order:${orderId}:gate-session` },
  );

  orders.set(orderId, {
    ...order,
    status: 'pending',
    gateSessionId: session.id,
  });

  res.json({
    clientSecret: session.client_secret,
    publishableKey: process.env.OBIT_GATE_PUBLISHABLE_KEY,
  });
});

app.post('/webhooks/0gate', express.raw({ type: 'application/json' }), (req, res) => {
  let event;

  try {
    event = gate.webhooks.constructEvent(
      req.body,
      req.headers['gate-signature'],
      process.env.OBIT_GATE_WEBHOOK_SECRET!,
    );
  } catch (error) {
    if (error instanceof WebhookSignatureError) return res.sendStatus(400);
    return res.sendStatus(500);
  }

  const orderId = String(event.data?.metadata?.order_id ?? '');
  const order = orders.get(orderId);

  if (!order || order.lastEventId === event.id) {
    return res.sendStatus(200);
  }

  if (event.type === 'gate_session.completed') {
    orders.set(orderId, { ...order, status: 'paid', lastEventId: event.id });
  }

  if (event.type === 'gate_session.cancelled') {
    orders.set(orderId, { ...order, status: 'cancelled', lastEventId: event.id });
  }

  if (event.type === 'gate_session.expired') {
    orders.set(orderId, { ...order, status: 'expired', lastEventId: event.id });
  }

  res.sendStatus(200);
});

app.listen(4242, () => {
  console.log('0Gate sample listening on http://localhost:4242');
});

For a real app, replace the in-memory map with your database. Persist the order id, session id, idempotency key, event id, event type, and timestamps.

Browser mount

checkout.ts
import { GateRamp } from '@0bit/gate/browser';

async function startCheckout(orderId: string) {
  const response = await fetch('/api/0gate/session', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ orderId }),
  });

  const { clientSecret, publishableKey } = await response.json();

  const ramp = new GateRamp({
    publishableKey,
    clientSecret,
    environment: 'sandbox',
  });

  await ramp.mount('#gate-container', {
    onSuccess: ({ sessionId }) => {
      window.location.href = `/checkout/${orderId}/pending?session=${encodeURIComponent(sessionId)}`;
    },
    onClose: () => {
      window.location.href = `/checkout/${orderId}`;
    },
  });
}

React mount

Checkout.tsx
import { useEffect, useState } from 'react';
import { RampCheckout } from '@0bit/gate/react';

export function Checkout({ orderId }: { orderId: string }) {
  const [session, setSession] = useState<{
    clientSecret: string;
    publishableKey: string;
  } | null>(null);

  useEffect(() => {
    fetch('/api/0gate/session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ orderId }),
    })
      .then((response) => response.json())
      .then(setSession);
  }, [orderId]);

  if (!session) return null;

  return (
    <RampCheckout
      publishableKey={session.publishableKey}
      clientSecret={session.clientSecret}
      environment="sandbox"
      onSuccess={({ sessionId }) => {
        window.location.href = `/checkout/${orderId}/pending?session=${encodeURIComponent(sessionId)}`;
      }}
    />
  );
}

Production notes

AreaRequirement
KeysStore sk_* and whsec_* in server-side secret storage only.
OriginsConfigure exact embed origins and return URLs before using the widget.
WebhooksUse a raw-body parser, verify Gate-Signature, dedupe on event.id, and return 2xx only after durable handling.
IdempotencyUse one stable idempotency key per logical order/session creation attempt.
SupportLog request ids, session ids, event ids, order ids, environment, and timestamps. Do not log secrets or raw PII payloads.

What is not proven by this sample

  • It does not prove live regional availability or KYC/KYB eligibility.
  • It does not prove 0Pools, 0Base, or 0Link public access.
  • It does not replace legal, compliance, or product approval.
  • It does not guarantee npm/PyPI registry publishing status.

For production partner help, contact [email protected] with environment, session id, request id, event id, and the smallest support-safe reproduction.

On this page