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:
npm install obol-x402-sdk
# or
pnpm add obol-x402-sdkThe 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
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
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.
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 failureAPI Reference
new Obol(apiKey, options?)
Creates a new Obol client.
| Parameter | Type | Description |
|---|---|---|
| apiKey | string | Your Obol API key from the dashboard |
| options.baseUrl | string? | Override Obol API URL. Default: https://api.obol.dev |
| options.cacheTtl | number? | 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.
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.
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.
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
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
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:
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
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.
const payment = await obol.protect("free-endpoint-uuid", request);
// payment.verified === true
// payment.payer === nullError 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.
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
| Variable | Required | Description |
|---|---|---|
| OBOL_API_KEY | Yes | Your project API key from the Obol dashboard |