npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ribbit-pay

v0.0.3

Published

Official Node.js / TypeScript SDK for the Ribbit Pay API

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

  • Node.js 18 or later (uses native fetch and crypto)
  • An active Ribbit Pay merchant account

Installation

npm install ribbit-pay
# or
yarn add ribbit-pay
# or
pnpm add ribbit-pay

No runtime dependencies. Zero bloat.


API key setup

  1. Sign up or log in at merchant.ribbitpay.com.
  2. Complete merchant onboarding (business details, wallet addresses).
  3. Go to Developers → API Keys and create a new key.
  4. 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 order

Client 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 undefined

Polling 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 removed

Wallet

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

  1. Log in to merchant.ribbitpay.com.
  2. Go to Settings → Webhooks.
  3. Enter your HTTPS endpoint URL (must be publicly reachable).
  4. Select the events you want to receive (leave blank to receive all events).
  5. 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.confirmed

Your 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.