Documentation

Obol is the easiest way to monetize your APIs with real-time micropayments. Drop in our SDK and every API call becomes a paid RLUSD micro-transaction on the XRP Ledger using the x402 payment protocol.

No Stripe, no proxy, no custody. Just one line of code.


Installation

Install the SDK with your preferred package manager:

terminal
npm install obol-x402-sdk
# or
pnpm add obol-x402-sdk

The SDK has zero runtime dependencies and requires Node.js 18+.


Quick Start

1. Create a client

Initialize the Obol client once with your API key from the dashboard.

lib/obol.ts
// lib/obol.ts
import { Obol } from "obol-x402-sdk";

export const obol = new Obol(process.env.OBOL_API_KEY!);

2. Protect an endpoint

Wrap any route handler with obol.protect(). If the caller hasn't paid, a 402 response is returned automatically. If they have, you get a verified payment gate with settle() and fail().

app/api/weather/route.ts
// app/api/weather/route.ts
import { obol } from "@/lib/obol";

export async function GET(request: Request) {
  const payment = await obol.protect("your-endpoint-uuid", request);
  if (!payment.verified) return payment.response; // 402

  try {
    const data = { goodWeather: true };
    await payment.settle();
    return Response.json(data);
  } catch (error) {
    await payment.fail(error);
    return Response.json({ error: "Something went wrong" }, { status: 500 });
  }
}

That's it. No headers to parse, no facilitator URLs to manage, no payment logic to write.


How It Works

The key insight: payment is verified before your business logic runs, but only settled after it succeeds. If your code throws, the buyer keeps their money.

flow
Request arrives
    |
    v
obol.protect("endpoint-uuid", request)
    |
    |-- Fetches endpoint config from Obol API (cached 5 min)
    |
    |-- No payment header?
    |   -> Returns 402 Response with x402 payment instructions
    |
    |-- Has PAYMENT-SIGNATURE header?
    |   -> Verifies with XRPL facilitator
    |   -> If invalid: returns 402 with error
    |   -> If valid: returns gate with settle() and fail()
    |
payment.settle()   -- Submits tx to XRPL ledger + logs success
payment.fail(err)  -- Skips settlement + logs failure

API Reference

new Obol(apiKey, options?)

Creates a new Obol client.

ParameterTypeDescription
apiKeystringYour Obol API key from the dashboard
options.baseUrlstring?Override Obol API URL. Default: https://api.obol.dev
options.cacheTtlnumber?Config cache TTL in ms. Default: 300000 (5 min)

obol.protect(endpointId, request)

Returns a PaymentGate — either a 402 response to return, or a verified payment to settle.

types
type PaymentGate =
  | { verified: false; response: Response }
  | {
      verified: true;
      payer: string;       // XRPL address of the buyer
      network: string;     // "xrpl:1" (testnet) or "xrpl:0" (mainnet)
      amount: string;      // Amount in drops
      asset: string;       // e.g. "XRP"
      settle(): Promise<SettlementResult>;
      fail(error?: unknown): Promise<void>;
    };

payment.settle()

Submits the transaction to the XRPL ledger and logs success to your dashboard. Safe to call multiple times — subsequent calls return the cached result.

usage
const result = await payment.settle();
// { success: boolean, txHash: string | null, error: string | null }

payment.fail(error?)

Logs a failure to your dashboard without settling. The buyer's funds are never touched. No-op if settle() was already called.

usage
await payment.fail(new Error("Database write failed"));

Framework Examples

The SDK uses Web standard Request and Response objects. It works with any framework that supports them.

Next.js App Router

app/api/resource/route.ts
// app/api/resource/route.ts
import { obol } from "@/lib/obol";

export async function GET(request: Request) {
  const payment = await obol.protect("endpoint-uuid", request);
  if (!payment.verified) return payment.response;

  const data = { message: "Premium content" };
  await payment.settle();
  return Response.json(data);
}

Hono

hono-server.ts
import { Hono } from "hono";
import { Obol } from "obol-x402-sdk";

const obol = new Obol(process.env.OBOL_API_KEY!);
const app = new Hono();

app.get("/api/resource", async (c) => {
  const payment = await obol.protect("endpoint-uuid", c.req.raw);
  if (!payment.verified) return payment.response;

  await payment.settle();
  return c.json({ message: "Paid content" });
});

Express

Express doesn't use Web standard Request objects, so you need a small conversion:

express-server.ts
import express from "express";
import { Obol } from "obol-x402-sdk";

const obol = new Obol(process.env.OBOL_API_KEY!);
const app = express();

app.get("/api/resource", async (req, res) => {
  // Convert Express req to Web Request
  const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
  const headers = new Headers();
  for (const [key, val] of Object.entries(req.headers)) {
    if (typeof val === "string") headers.set(key, val);
  }
  const webReq = new Request(url, { method: req.method, headers });

  const payment = await obol.protect("endpoint-uuid", webReq);

  if (!payment.verified) {
    for (const [k, v] of payment.response.headers.entries()) res.set(k, v);
    res.status(payment.response.status).send(await payment.response.text());
    return;
  }

  const data = { message: "Paid content" };
  await payment.settle();
  res.json(data);
});

Cloudflare Workers

worker.ts
import { Obol } from "obol-x402-sdk";

const obol = new Obol("your-api-key");

export default {
  async fetch(request: Request): Promise<Response> {
    const payment = await obol.protect("endpoint-uuid", request);
    if (!payment.verified) return payment.response;

    await payment.settle();
    return Response.json({ message: "Paid content" });
  },
};

Free Endpoints

Set price to "0" in the dashboard. protect() returns verified: true immediately with payer: null — no payment flow, just analytics logging.

usage
const payment = await obol.protect("free-endpoint-uuid", request);
// payment.verified === true
// payment.payer === null

Error Handling

The recommended pattern is to wrap your business logic in a try/catch and call fail()on errors. This ensures the buyer's funds are never taken when your service fails.

route.ts
export async function GET(request: Request) {
  const payment = await obol.protect("endpoint-uuid", request);
  if (!payment.verified) return payment.response;

  try {
    // Your business logic here
    const result = await doExpensiveWork();
    await payment.settle();
    return Response.json(result);
  } catch (error) {
    // Buyer keeps their money — failure is logged to your dashboard
    await payment.fail(error);
    return Response.json({ error: "Internal error" }, { status: 500 });
  }
}

Idempotency

Both settle() and fail() are idempotent. Calling settle() twice returns the cached result from the first call. Calling fail() after settle() is a no-op.


Environment Variables

VariableRequiredDescription
OBOL_API_KEYYesYour project API key from the Obol dashboard