ribbit-pay
v0.0.3
Published
Official Node.js / TypeScript SDK for the Ribbit Pay API
Maintainers
Readme
ribbit-pay
Official Node.js / TypeScript SDK for the Ribbit Pay API.
Accept crypto payments on the Supra blockchain. Create hosted checkout sessions, manage subscriptions, and receive real-time webhook notifications — all from your Node.js backend.
Table of contents
- Requirements
- Installation
- API key setup
- Environments
- Quick start
- Client options
- Checkout
- Subscription management
- Wallet
- Webhooks
- Error handling
- TypeScript types
- Checkout status lifecycle
- Supported networks
Requirements
- Node.js 18 or later (uses native
fetchandcrypto) - An active Ribbit Pay merchant account
Installation
npm install ribbit-pay
# or
yarn add ribbit-pay
# or
pnpm add ribbit-payNo runtime dependencies. Zero bloat.
API key setup
- Sign up or log in at merchant.ribbitpay.com.
- Complete merchant onboarding (business details, wallet addresses).
- Go to Developers → API Keys and create a new key.
- Copy the key — it is shown once. Store it securely (e.g. in an environment variable).
Never expose your API key in client-side code. All API calls must come from your backend server.
Environments
Your API key is tied to an environment. Testnet keys only create testnet checkouts; mainnet keys only create mainnet checkouts.
| Key prefix | Environment | Use for |
| --- | --- | --- |
| rb_mainnet_... | Supra Mainnet | Production |
| rb_testnet_... | Supra Testnet | Development & QA |
Filter testnet events in your webhook handler using event.data.environment.
Quick start
import { RibbitPay } from "ribbit-pay";
const ribbit = new RibbitPay({
apiKey: process.env.RIBBIT_API_KEY!,
});
// Create a checkout session
const checkout = await ribbit.checkout.create({
paymentType: "direct",
amount: "10.00",
currency: "SUPRA",
network: "supra",
tokenAddress: "0x1::supra_coin::SupraCoin",
description: "Order #1234",
successUrl: "https://yoursite.com/success",
cancelUrl: "https://yoursite.com/cancel",
metadata: { orderId: "1234", customerId: "cust_abc" },
});
// Redirect your customer to the hosted payment page
console.log(checkout.checkoutUrl);
// https://checkout.ribbitpay.com/ch_abc123
// Listen for payment.confirmed webhook to fulfill the orderClient options
const ribbit = new RibbitPay({
// Required. Your merchant API key.
apiKey: "rb_mainnet_...",
// Optional. Override the base URL. Default: "https://api.ribbitpay.com"
baseUrl: "https://api.ribbitpay.com",
// Optional. Request timeout in milliseconds. Default: 30000
timeout: 30_000,
// Optional. Max automatic retries on 5xx / network errors. Default: 2
// Uses exponential backoff with jitter. Does NOT retry 4xx errors.
maxRetries: 2,
});Checkout
Create a checkout
ribbit.checkout.create(params) → Promise<Checkout>
Create a new hosted checkout session and redirect your customer to checkoutUrl.
Direct (one-time) payment:
const checkout = await ribbit.checkout.create({
paymentType: "direct",
amount: "10.00", // Decimal string, e.g. "10.00"
currency: "SUPRA", // Currently: "SUPRA"
network: "supra", // See supported networks below
// List of accepted tokens — the wallet shows these as payment options.
// The customer picks one at checkout. Use multiple to accept several tokens.
tokenAddress: "0x1::supra_coin::SupraCoin",
description: "Order #1234",
successUrl: "https://yoursite.com/success",
cancelUrl: "https://yoursite.com/cancel",
metadata: {
orderId: "1234",
customerId: "cust_abc",
plan: "starter",
},
});
// checkout.checkoutId → "ch_abc123"
// checkout.checkoutUrl → "https://checkout.ribbitpay.com/ch_abc123"
// checkout.status → "created"
// checkout.expiresAt → "2026-03-15T10:30:00Z" (30 min TTL)
res.redirect(checkout.checkoutUrl);Response type:
interface Checkout {
checkoutId: string;
checkoutUrl: string; // Redirect your customer here
status: CheckoutStatus; // "created" initially
expiresAt: string; // ISO datetime, 30 min from creation
}Idempotency: Requests with the same amount, tokenAddress, and network from the same merchant within a 5-minute window return the existing session. Safe to retry on network failure.
Create a subscription checkout
const checkout = await ribbit.checkout.create({
paymentType: "recurring",
amount: "0.00",
currency: "SUPRA",
network: "supra",
tokenAddress: "0x1::supra_coin::SupraCoin",
description: "Pro Plan — monthly",
mandate: {
interval: "monthly", // "min"|"hourly"|"daily"|"weekly"|"monthly"|"yearly"
amountPerCycle: 5.00, // Amount in display units
maxPayments: 12, // Maximum billing cycles
tokenAddressPerCycle: "0x1::supra_coin::SupraCoin", // Token used for recurring billing
reference: "plan_pro_monthly", // Optional merchant reference
startAt: "2026-04-01T00:00:00Z", // Optional: when billing starts
},
metadata: { planId: "pro_monthly" },
});
res.redirect(checkout.checkoutUrl);The customer is prompted to approve an on-chain billing mandate in the Ribbit wallet app. After approval, Ribbit charges them automatically each cycle.
Retrieve a checkout
ribbit.checkout.retrieve(checkoutId) → Promise<SubscriptionDetails>
Retrieve full checkout and subscription details. Requires your API key.
const details = await ribbit.checkout.retrieve("ch_abc123");
// Checkout-level fields
console.log(details.checkoutId); // "ch_abc123"
console.log(details.checkoutStatus); // "completed"
console.log(details.txHash); // "0xdef456..."
console.log(details.paymentType); // "recurring"
// Subscription on-chain state (recurring only)
console.log(details.subscriptionId); // 42
console.log(details.status); // 1 (active)
console.log(details.statusLabel); // "active"
console.log(details.customer); // "0xCustomerWalletAddress..."
console.log(details.createdAt); // 1741824000 (epoch seconds)
// Live mandate / billing state
const m = details.mandate!;
console.log(m.totalPaymentsMade); // 3
console.log(m.nextPaymentTime); // 1744416000 (epoch seconds)
console.log(m.amountPerPeriod); // 5000000000 (octas)
console.log(m.pausedUntil); // 0 (not paused)
// Pending merchant-initiated action (if any)
console.log(details.pendingAction); // { action: "pause", ... } or undefinedPolling for status:
const TERMINAL = new Set([
"completed",
"failed",
"rejected",
"subscription_registered",
"subscription_initiated",
]);
async function pollUntilDone(checkoutId: string) {
while (true) {
const details = await ribbit.checkout.retrieve(checkoutId);
if (TERMINAL.has(details.checkoutStatus)) {
return details;
}
await new Promise((r) => setTimeout(r, 2000)); // poll every 2s
}
}Subscription management
All subscription management actions create a pending request that is displayed to the customer in the Ribbit wallet app. The customer must approve before any on-chain action executes.
Only one request can be pending at a time. Call cancelRequest first if you need to change the action.
Pause a subscription
ribbit.subscriptions.pause(checkoutId, { durationSeconds }) → Promise<SubscriptionRequest>
// Pause billing for 30 days
const request = await ribbit.subscriptions.pause("ch_abc123", {
durationSeconds: 30 * 24 * 60 * 60, // 2592000 seconds
});
console.log(request.requestId); // "req_xyz789"
console.log(request.status); // "pending"
console.log(request.message); // "Request sent to customer for approval."Cancel a subscription
ribbit.subscriptions.cancel(checkoutId) → Promise<SubscriptionRequest>
const request = await ribbit.subscriptions.cancel("ch_abc123");
console.log(request.requestId); // "req_xyz789"
console.log(request.status); // "pending"Update a subscription plan
ribbit.subscriptions.updatePlan(checkoutId, params) → Promise<SubscriptionRequest>
Change the per-cycle charge amount or total payment cap. newMaxPayments must be greater than the number of payments already made.
// Upgrade from 5 SUPRA to 10 SUPRA per month, extend cap to 24 cycles
const request = await ribbit.subscriptions.updatePlan("ch_abc123", {
newAmountPerPeriod: 10_000_000_000, // 10 SUPRA in octas (1 SUPRA = 1_000_000_000 octas)
newMaxPayments: 24,
});
console.log(request.requestId); // "req_xyz789"Cancel a pending request
ribbit.subscriptions.cancelRequest(checkoutId, requestId) → Promise<void>
Cancel a merchant-initiated request before the customer approves or rejects it. No on-chain action is taken.
await ribbit.subscriptions.cancelRequest("ch_abc123", "req_xyz789");
// resolves with void — request removedWallet
Query on-chain wallet balances and browse supported tokens with live USD prices from the Supra Oracle.
Get wallet balance
ribbit.wallet.getBalance(params) → Promise<WalletBalance>
import { RibbitPay, Token } from "ribbit-pay";
const ribbit = new RibbitPay({ apiKey: process.env.RIBBIT_API_KEY! });
// SUPRA balance on mainnet (all params optional except address)
const balance = await ribbit.wallet.getBalance({
address: "0xabc...",
chain: "supra", // default: "supra"
network: "mainnet", // "mainnet" | "testnet" — default: "mainnet"
tokenAddress: Token.SUPRA, // default: Token.SUPRA
});
console.log(balance.symbol); // "SUPRA"
console.log(balance.balance); // "500.0" (human-readable)
console.log(balance.balanceRaw); // "50000000000" (raw on-chain octas)
console.log(balance.priceUsd); // 0.000445 (null if oracle unavailable)
console.log(balance.valueUsd); // 0.2225 (null if oracle unavailable)
console.log(balance.change24h); // -1.5 (% change, null for stablecoins)Testnet balance:
const balance = await ribbit.wallet.getBalance({
address: "0xabc...",
network: "testnet",
});CASH stablecoin balance:
const balance = await ribbit.wallet.getBalance({
address: "0xabc...",
tokenAddress: Token.CASH,
});
console.log(balance.symbol); // "CASH"
console.log(balance.priceUsd); // 1.0 (always — USD-pegged, no oracle needed)
console.log(balance.change24h); // null (stablecoin, no 24h stats)Response type:
interface WalletBalance {
address: string;
chain: string; // e.g. "supra"
network: string; // "mainnet" | "testnet"
tokenAddress: string;
symbol: string | null; // null for unknown tokens
name: string | null;
balanceRaw: string; // raw on-chain value as string
balance: string; // human-readable (raw ÷ decimals)
decimals: number; // divisor, e.g. 100000000 for SUPRA
priceUsd: number | null; // null if oracle unavailable
valueUsd: number | null; // balance × priceUsd
change24h: number | null; // 24-hour price change %, null for stablecoins
high24h: number | null;
low24h: number | null;
}Get all balances (portfolio view)
ribbit.wallet.getAllBalances(params) → Promise<AllBalances>
Fetch balances for all supported tokens in a single RPC call. More efficient than calling getBalance() per token. Tokens with zero balance are still included.
const portfolio = await ribbit.wallet.getAllBalances({
address: "0xabc...",
chain: "supra", // default: "supra"
network: "mainnet", // default: "mainnet"
});
console.log(portfolio.totalValueUsd); // 1.2225 (null if no price data)
for (const token of portfolio.tokens) {
console.log(`${token.symbol}: ${token.balance} ($${token.valueUsd ?? "N/A"})`);
// SUPRA: 1557.80 ($0.695)
// CASH: 0.003 ($0.003)
}Testnet:
const portfolio = await ribbit.wallet.getAllBalances({
address: "0xabc...",
network: "testnet",
});Response type:
interface AllBalances {
address: string;
chain: string;
network: string;
/** Sum of all valueUsd fields. null if no price data available. */
totalValueUsd: number | null;
tokens: TokenBalance[];
}
interface TokenBalance {
tokenAddress: string;
symbol: string;
name: string;
balanceRaw: string; // raw on-chain value
balance: string; // human-readable (raw ÷ decimals)
decimals: number;
priceUsd: number | null;
valueUsd: number | null; // balance × priceUsd
change24h: number | null;
high24h: number | null;
low24h: number | null;
}List supported tokens
ribbit.wallet.listTokens(params?) → Promise<TokenInfo[]>
Returns all tokens supported by Ribbit Pay with live USD prices.
const tokens = await ribbit.wallet.listTokens();
// or with network:
const tokens = await ribbit.wallet.listTokens({ network: "testnet" });
for (const token of tokens) {
console.log(`${token.symbol}: $${token.priceUsd ?? "N/A"}`);
// SUPRA: $0.000445
// CASH: $1
}Response type:
interface TokenInfo {
tokenAddress: string;
symbol: string;
name: string;
decimals: number;
tradingPair: string | null; // Oracle pair name, null for stablecoins
chain: string;
network: string;
priceUsd: number | null;
change24h: number | null;
high24h: number | null;
low24h: number | null;
}Supported tokens
| Constant | Symbol | Address | Price source |
| --- | --- | --- | --- |
| Token.SUPRA | SUPRA | 0x1::supra_coin::SupraCoin | Supra Oracle (live) |
| Token.CASH | CASH | 0x9176f70f...::cdp_multi::CASH | Fixed $1.00 (USD-pegged) |
import { Token } from "ribbit-pay";
Token.SUPRA // "0x1::supra_coin::SupraCoin"
Token.CASH // "0x9176f70f125199a3e3d5549ce795a8e906eed75901d535ded623802f15ae3637::cdp_multi::CASH"Webhooks
Webhook setup
- Log in to merchant.ribbitpay.com.
- Go to Settings → Webhooks.
- Enter your HTTPS endpoint URL (must be publicly reachable).
- Select the events you want to receive (leave blank to receive all events).
- Click Save — a Signing Secret (
whsec_...) is generated and shown once.
Store the signing secret securely. It cannot be retrieved again. You can rotate it in Settings → Webhooks → Rotate Secret.
Ribbit sends a POST request to your endpoint with these headers:
Content-Type: application/json
X-Ribbit-Signature: sha256=<hmac_sha256_hex>
X-Ribbit-Event: payment.confirmedYour endpoint must return a 2xx response within 10 seconds.
Verify and parse events
ribbit.webhooks.constructEvent(rawBody, signature, secret) → WebhookEvent
Always verify the signature using the raw request body — not a re-serialized version. Pass the Buffer or raw string before any JSON parsing.
import { RibbitPay, RibbitWebhookError } from "ribbit-pay";
const ribbit = new RibbitPay({ apiKey: process.env.RIBBIT_API_KEY! });
function handleWebhook(rawBody: Buffer, signatureHeader: string) {
let event;
try {
event = ribbit.webhooks.constructEvent(
rawBody,
signatureHeader,
process.env.RIBBIT_WEBHOOK_SECRET!, // whsec_...
);
} catch (err) {
if (err instanceof RibbitWebhookError) {
// Invalid signature — return 400
throw err;
}
throw err;
}
// event is a fully-typed discriminated union
switch (event.type) {
case "payment.confirmed":
console.log("Paid:", event.data.txHash);
// Fulfill order using event.data.checkoutId
break;
case "subscription.created":
console.log("Subscription:", event.data.subscriptionId);
break;
case "subscription.renewed":
console.log("Renewed cycle:", event.data.paymentNumber);
break;
case "subscription.cancelled":
console.log("Reason:", event.data.reason);
break;
case "subscription.paused":
console.log("Paused until:", new Date(event.data.pausedUntil * 1000));
break;
}
}Express.js webhook handler
import express from "express";
import { RibbitPay, RibbitWebhookError } from "ribbit-pay";
const ribbit = new RibbitPay({ apiKey: process.env.RIBBIT_API_KEY! });
const app = express();
// IMPORTANT: Use express.raw() BEFORE express.json() for webhook routes.
// The signature is verified against the raw body bytes.
app.post(
"/webhooks/ribbit",
express.raw({ type: "application/json" }),
async (req, res) => {
const signature = req.headers["x-ribbit-signature"] as string;
let event;
try {
event = ribbit.webhooks.constructEvent(
req.body, // Buffer
signature,
process.env.RIBBIT_WEBHOOK_SECRET!,
);
} catch (err) {
if (err instanceof RibbitWebhookError) {
return res.status(400).json({ error: "Invalid signature" });
}
console.error("Webhook error:", err);
return res.status(500).json({ error: "Internal error" });
}
// Return 200 immediately, process asynchronously
res.json({ received: true });
// Process event in background
setImmediate(async () => {
try {
await processEvent(event);
} catch (err) {
console.error("Failed to process event:", event.id, err);
}
});
},
);
async function processEvent(event: ReturnType<typeof ribbit.webhooks.constructEvent>) {
// Use event.id to deduplicate — the same event may arrive more than once
switch (event.type) {
case "payment.confirmed":
await fulfillOrder(event.data.checkoutId);
break;
case "subscription.created":
await activateSubscription(event.data.checkoutId, event.data.subscriptionId);
break;
case "subscription.cancelled":
await deactivateSubscription(event.data.checkoutId);
break;
}
}
declare function fulfillOrder(checkoutId: string): Promise<void>;
declare function activateSubscription(checkoutId: string, subscriptionId: string): Promise<void>;
declare function deactivateSubscription(checkoutId: string): Promise<void>;Next.js App Router webhook handler
// app/api/webhooks/ribbit/route.ts
import { NextRequest, NextResponse } from "next/server";
import { RibbitPay, RibbitWebhookError } from "ribbit-pay";
const ribbit = new RibbitPay({ apiKey: process.env.RIBBIT_API_KEY! });
export async function POST(req: NextRequest) {
// Read raw body as text — do NOT parse as JSON first
const rawBody = await req.text();
const signature = req.headers.get("x-ribbit-signature") ?? "";
let event;
try {
event = ribbit.webhooks.constructEvent(
rawBody,
signature,
process.env.RIBBIT_WEBHOOK_SECRET!,
);
} catch (err) {
if (err instanceof RibbitWebhookError) {
return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
}
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
// Return 200 immediately
// Use waitUntil if available in your hosting environment for background work
switch (event.type) {
case "payment.confirmed":
// event.data.checkoutId, event.data.txHash, event.data.amount
break;
case "subscription.created":
// event.data.subscriptionId, event.data.interval, event.data.amountPerCycle
break;
case "subscription.renewed":
// event.data.paymentNumber, event.data.txHash
break;
case "subscription.paused":
// event.data.pausedUntil (epoch seconds)
break;
case "subscription.updated":
// event.data.newAmountPerPeriod, event.data.newMaxPayments
break;
}
return NextResponse.json({ received: true });
}All webhook event types
payment.created
Fired when a checkout session is created.
{
id: "evt_a1b2c3",
type: "payment.created",
created: 1741824000, // epoch seconds
data: {
checkoutId: "ch_abc123",
merchantId: "4eb0e411-f755-4ca3-9167-8d956e8e03f1",
environment: "mainnet", // "mainnet" | "testnet"
amount: "10.00",
currency: "SUPRA",
status: "pending",
createdAt: "2026-03-15T10:00:00Z",
}
}payment.confirmed
Fired when a payment is confirmed on-chain. Use this to fulfill the order.
{
id: "evt_a1b2c3",
type: "payment.confirmed",
created: 1741824120,
data: {
checkoutId: "ch_abc123",
merchantId: "4eb0e411-f755-4ca3-9167-8d956e8e03f1",
environment: "mainnet",
amount: "10.00",
currency: "SUPRA",
txHash: "0xdef456...",
confirmedAt: "2026-03-15T10:02:00Z",
}
}payment.failed
Fired when a payment fails or the checkout session expires.
{
id: "evt_a1b2c3",
type: "payment.failed",
created: 1741824240,
data: {
checkoutId: "ch_abc123",
merchantId: "4eb0e411-f755-4ca3-9167-8d956e8e03f1",
environment: "mainnet",
amount: "10.00",
currency: "SUPRA",
status: "failed",
failedAt: "2026-03-15T10:05:00Z",
}
}subscription.created
Fired when a recurring mandate is registered on-chain.
{
id: "evt_a1b2c3",
type: "subscription.created",
created: 1741824000,
data: {
checkoutId: "ch_abc123",
subscriptionId: "sub_xyz789",
merchantId: "4eb0e411-f755-4ca3-9167-8d956e8e03f1",
environment: "mainnet",
amountPerCycle: "5.00",
currency: "SUPRA",
interval: "monthly",
maxPayments: 12,
network: "supra",
status: "active",
createdAt: "2026-03-15T10:00:00Z",
}
}subscription.renewed
Fired each time a subscription cycle is charged successfully.
{
id: "evt_a1b2c3",
type: "subscription.renewed",
created: 1744416000,
data: {
checkoutId: "ch_abc123",
subscriptionId: "sub_xyz789",
merchantId: "4eb0e411-f755-4ca3-9167-8d956e8e03f1",
environment: "mainnet",
amount: "5.00",
currency: "SUPRA",
txHash: "0xdef456...",
paymentNumber: 2, // Which cycle this is (1-indexed)
renewedAt: "2026-04-15T10:00:00Z",
}
}subscription.cancelled
Fired when a subscription is cancelled (by the merchant, customer, or system).
{
id: "evt_a1b2c3",
type: "subscription.cancelled",
created: 1741824000,
data: {
checkoutId: "ch_abc123",
subscriptionId: "sub_xyz789",
merchantId: "4eb0e411-f755-4ca3-9167-8d956e8e03f1",
environment: "mainnet",
reason: "user_cancelled", // "user_cancelled" | "merchant_requested" | "payment_failed" | ...
cancelledAt: "2026-03-20T14:00:00Z",
}
}subscription.paused
Fired when billing is paused for a subscription.
{
id: "evt_a1b2c3",
type: "subscription.paused",
created: 1741824000,
data: {
checkoutId: "ch_abc123",
subscriptionId: "sub_xyz789",
merchantId: "4eb0e411-f755-4ca3-9167-8d956e8e03f1",
environment: "mainnet",
pausedUntil: 1744416000, // Epoch seconds when billing resumes
pausedAt: "2026-03-15T10:00:00Z",
}
}subscription.resumed
Fired when a paused subscription resumes billing.
{
id: "evt_a1b2c3",
type: "subscription.resumed",
created: 1744416000,
data: {
checkoutId: "ch_abc123",
subscriptionId: "sub_xyz789",
merchantId: "4eb0e411-f755-4ca3-9167-8d956e8e03f1",
environment: "mainnet",
resumedAt: "2026-04-15T10:00:00Z",
}
}subscription.updated
Fired when a subscription plan (amount or payment cap) is updated.
{
id: "evt_a1b2c3",
type: "subscription.updated",
created: 1741824000,
data: {
checkoutId: "ch_abc123",
subscriptionId: "sub_xyz789",
merchantId: "4eb0e411-f755-4ca3-9167-8d956e8e03f1",
environment: "mainnet",
newAmountPerPeriod: 10_000_000_000, // In octas (smallest unit)
newMaxPayments: 24,
updatedAt: "2026-03-15T10:00:00Z",
}
}Error handling
All errors extend the base RibbitError class.
import {
RibbitPay,
RibbitApiError,
RibbitWebhookError,
RibbitTimeoutError,
} from "ribbit-pay";API errors (RibbitApiError)
Thrown when the API returns a 4xx or 5xx response.
try {
const checkout = await ribbit.checkout.retrieve("ch_nonexistent");
} catch (err) {
if (err instanceof RibbitApiError) {
console.log(err.status); // HTTP status code: 404
console.log(err.code); // "NOT_FOUND"
console.log(err.message); // "Checkout not found."
console.log(err.details); // Validation detail array (non-empty for VALIDATION_ERROR)
// Convenience methods
err.isNotFound(); // true if status === 404
err.isUnauthorized(); // true if status === 401
err.isForbidden(); // true if status === 403
err.isServerError(); // true if status >= 500
}
}Error codes:
| HTTP | code | When it occurs |
| --- | --- | --- |
| 400 | BAD_REQUEST | Malformed request |
| 400 | VALIDATION_ERROR | Request body failed schema validation |
| 401 | UNAUTHORIZED | API key missing, invalid, or inactive |
| 403 | FORBIDDEN | Origin blocked or merchant mismatch |
| 404 | NOT_FOUND | Checkout or subscription not found |
| 500 | INTERNAL_ERROR | Unexpected server error |
Validation errors
For VALIDATION_ERROR responses, err.details contains field-level detail:
try {
await ribbit.checkout.create({ ... });
} catch (err) {
if (err instanceof RibbitApiError && err.code === "VALIDATION_ERROR") {
for (const detail of err.details) {
console.log(`${detail.field}: ${detail.message}`);
// e.g. "body.mandate: mandate is required when payment_type is recurring"
}
}
}Timeout errors (RibbitTimeoutError)
Thrown when a request exceeds the configured timeout.
try {
await ribbit.checkout.create({ ... });
} catch (err) {
if (err instanceof RibbitTimeoutError) {
console.log(err.message); // "Request timed out after 30000ms."
// Safe to retry — checkout creation is idempotent within a 5-minute window
}
}Webhook errors (RibbitWebhookError)
Thrown by constructEvent when signature verification fails.
try {
const event = ribbit.webhooks.constructEvent(rawBody, sig, secret);
} catch (err) {
if (err instanceof RibbitWebhookError) {
// Return 400 — do not process the payload
res.status(400).send("Invalid signature");
}
}Retry behavior
The client automatically retries failed requests on 5xx errors and network failures, with exponential backoff and jitter. Configure with maxRetries (default: 2). 4xx errors are never retried.
TypeScript types
The SDK is written in TypeScript and ships with full type definitions. All types are exported from the main entry point.
import type {
// Client
RibbitPayOptions,
// Checkout
CreateCheckoutParams,
CreateDirectCheckoutParams,
CreateRecurringCheckoutParams,
Checkout,
CheckoutStatus,
// Subscription
SubscriptionDetails,
MandateConfig,
MandateParams,
SubscriptionRequest,
PauseSubscriptionParams,
UpdatePlanParams,
SubscriptionStatus,
SubscriptionStatusLabel,
// Wallet
WalletBalance,
AllBalances,
TokenBalance,
TokenInfo,
// Webhooks
WebhookEvent,
WebhookEventType,
PaymentCreatedData,
PaymentConfirmedData,
PaymentFailedData,
SubscriptionCreatedData,
SubscriptionRenewedData,
SubscriptionCancelledData,
SubscriptionPausedData,
SubscriptionResumedData,
SubscriptionUpdatedData,
// Shared
Network,
Currency,
PaymentType,
MandateInterval,
Environment,
} from "ribbit-pay";Narrowing webhook events with TypeScript:
import type { WebhookEvent, PaymentConfirmedData } from "ribbit-pay";
function handleEvent(event: WebhookEvent) {
if (event.type === "payment.confirmed") {
// TypeScript narrows event.data to PaymentConfirmedData
const data: PaymentConfirmedData = event.data;
console.log(data.txHash); // fully typed
}
}Checkout status lifecycle
| Status | Description |
| --- | --- |
| created | Session initialised, waiting for customer |
| verified_wallet | Customer connected their wallet |
| ready_to_pay | Customer reviewed amount, ready to sign |
| pending_signature | Awaiting wallet signature |
| signed | Transaction signed, broadcasting to chain |
| completed | Payment confirmed on-chain |
| rejected | Customer rejected or cancelled |
| failed | Transaction failed or timed out |
| expired | Session expired after 30 minutes |
| subscription_registered | Recurring mandate registered on-chain |
| subscription_initiated | First recurring payment initiated |
Terminal statuses (will never change): completed, failed, rejected, subscription_registered, subscription_initiated.
Subscription on-chain statuses (in SubscriptionDetails.status):
| Value | Label | Meaning |
| --- | --- | --- |
| 1 | active | Billing is active |
| 2 | cancelled | Cancelled permanently |
| 3 | completed | All cycles completed |
| 4 | paused | Billing paused temporarily |
Supported chains and networks
Ribbit Pay separates chain (the blockchain) from network (mainnet vs testnet).
Wallet API (chain + network)
// Mainnet
await ribbit.wallet.getBalance({ address: "0xabc", chain: "supra", network: "mainnet" });
// Testnet
await ribbit.wallet.getBalance({ address: "0xabc", chain: "supra", network: "testnet" });| chain | Description | Status |
| --- | --- | --- |
| supra | Supra Move VM | Live |
| EVM chains | Ethereum, Polygon, Base, etc. | Coming soon |
| network | Description |
| --- | --- |
| mainnet | Production (default) |
| testnet | Development & QA |
Checkout API (network)
The checkout create() and retrieve() calls use network to identify the Supra network context:
await ribbit.checkout.create({ ..., network: "supra" }); // supra mainnet| network value | Chain | Status |
| --- | --- | --- |
| supra | Supra (Move VM) mainnet | Live |
| supra_evm | Supra EVM | Coming soon |
| ethereum | Ethereum | Coming soon |
| polygon | Polygon | Coming soon |
| arbitrum | Arbitrum One | Coming soon |
| base | Base | Coming soon |
| optimism | Optimism | Coming soon |
| bnb | BNB Chain | Coming soon |
| aptos | Aptos | Coming soon |
| sui | Sui | Coming soon |
| linea | Linea | Coming soon |
License
MIT — see LICENSE for details.
Built with love by the Ribbit Pay team.
