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

@bitxpay/sdk

v0.1.0

Published

Official BitXPay server-side SDK for payment links, subscriptions and mass payouts.

Readme

BitXPay Node.js / TypeScript SDK

Official server-side SDK for the BitXPay API. Covers payment processing and subscriptions, plus webhook verification.

Mass payouts are not yet included in this SDK and will be added in a future release.

⚠️ Server-side only. This SDK signs requests with your Ed25519 private key. Never ship that key — or this SDK — to a browser, mobile app, or any client device. Run it on your backend only.

Contents

Requirements

  • Node.js 18+ — the SDK uses the built-in fetch and node:crypto, so it has zero runtime dependencies.
  • Works with both ESM (import) and CommonJS (require).

Installation

npm install @bitxpay/sdk
# or: pnpm add @bitxpay/sdk   /   yarn add @bitxpay/sdk
// ESM / TypeScript
import { BitxpayClient } from "@bitxpay/sdk";
// CommonJS
const { BitxpayClient } = require("@bitxpay/sdk");

Get your API credentials

  1. Sign in to the BitXPay dashboard and open Developers → API Keys.
  2. Create a key. Choose test for sandbox or live for production.
  3. You'll be shown two values once:
    • an API keybtxm_test_… (sandbox) or btxm_live_… (production)
    • an Ed25519 private keyEd25519:<base64>

Copy both immediately; the private key is never shown again. Store them as environment variables (or in your secrets manager) — never commit them.

# .env  (load with your process manager or a dotenv loader; do NOT commit this file)
BITXPAY_API_KEY=btxm_test_xxxxxxxxxxxx
BITXPAY_PRIVATE_KEY=Ed25519:MC4CAQAwBQYDK2VwBCIEI...

Each request is authenticated with three headers the SDK sets for you:

| Header | Value | | --- | --- | | X-API-Key | your API key | | X-API-Timestamp | RFC3339 timestamp (must be within a 5-minute window) | | X-API-Signature | base64 Ed25519 signature of METHOD + PATH + TIMESTAMP + BODY |

Quickstart

A complete program that creates a payment link and prints the checkout URL:

import { BitxpayClient } from "@bitxpay/sdk";

const bitxpay = new BitxpayClient({
  apiKey: process.env.BITXPAY_API_KEY!,
  privateKey: process.env.BITXPAY_PRIVATE_KEY!,
  environment: "sandbox", // switch to "production" when you go live
});

async function main() {
  const link = await bitxpay.payments.create({
    payment_name: "Order #1234",
    amount: 49.99,
    currency: "USD",
    customer_email: "[email protected]",
    success_url: "https://example.com/thanks",
  });

  console.log("Send your customer to:", link.payment_link);
  console.log("Status:", link.payment_status, "| id:", link.id);
}

main().catch((err) => {
  console.error("BitXPay request failed:", err);
  process.exit(1);
});

Run it:

BITXPAY_API_KEY=btxm_test_xxx BITXPAY_PRIVATE_KEY=Ed25519:xxx npx tsx quickstart.ts

Configuring the client

const bitxpay = new BitxpayClient({
  apiKey: process.env.BITXPAY_API_KEY!,
  privateKey: process.env.BITXPAY_PRIVATE_KEY!,
  environment: "production",
  timeoutMs: 30_000,
  maxRetries: 2,
});

| Option | Default | Description | | --- | --- | --- | | apiKey | — | Your merchant API key (required). | | privateKey | — | Ed25519 private key, Ed25519:<base64> (required). | | environment | "sandbox" | "sandbox"https://sandboxapi.bitxpay.com, "production"https://api.bitxpay.com. | | baseUrl | — | Override the API URL entirely (staging/self-hosted). Takes precedence over environment. | | timeoutMs | 30000 | Per-request timeout in milliseconds. | | maxRetries | 2 | Retries for 429/5xx/network errors (exponential backoff + jitter, honors Retry-After). | | defaultHeaders | {} | Extra headers added to every request. | | fetch | global fetch | Custom fetch implementation (for older runtimes or instrumentation). |

A single client is cheap and thread-safe — create one at startup and reuse it.

Payment processing

Payment links are hosted checkout pages your customers pay with crypto.

// Create
const link = await bitxpay.payments.create({
  payment_name: "Pro plan — annual",
  description: "12 months of Pro",
  amount: 199.0,
  currency: "USD",
  customer_id: "cus_42",
  customer_email: "[email protected]",
  order_id: "ORD-2026-0042",        // your reference, echoed back on webhooks
  success_url: "https://example.com/thanks",
  cancel_url: "https://example.com/pricing",
  expires_at: "2026-12-31T23:59:59Z",
  max_uses: 1,
  webhook_metadata: { plan: "pro-annual" },
});

// Retrieve
const fresh = await bitxpay.payments.get(link.id);
console.log(fresh.payment_status); // e.g. "processing", "completed", "expired"

// Change lifecycle state: "activate" | "deactivate" | "expire"
await bitxpay.payments.updateStatus(link.id, "deactivate");

// Delete
await bitxpay.payments.delete(link.id);

// List (newest first) with filters
const page = await bitxpay.payments.list({
  page: 1,
  page_size: 20,
  status: "completed",
  search: "ORD-2026",
  sort_by: "amount",
  sort_order: "desc",
});
console.log(`${page.totalItems} links, showing ${page.data.length}`);

// Supported currencies for the amount/currency fields
const currencies = await bitxpay.payments.listCurrencies();

Subscriptions

Plans are anchored to an on-chain contract: create the plan on-chain first, then register it with BitXPay so subscribers and recurring charges are tracked.

// Register a plan
const plan = await bitxpay.subscriptions.createPlan({
  tx_hash: "0xabc...",                  // on-chain plan creation tx
  plan_id: "1",
  version: "1",
  network_id: networkId,
  currency_id: currencyId,
  token: usdcAddress,
  amount: "10000000",                   // smallest-unit string
  formatted_amount: "10.00",
  currencies: [usdcAddress],
  interval: 2_592_000,                  // 30 days, in seconds
  trial_period: 604_800,                // 7 days
  name: "Pro Monthly",
  description: "Pro plan billed monthly",
});

await bitxpay.subscriptions.listPlans({ page: 1, page_size: 20 });
await bitxpay.subscriptions.getPlan(plan.id);
await bitxpay.subscriptions.updatePlan(plan.id, { status: "inactive" });

// Subscribers
const subscriber = await bitxpay.subscriptions.createSubscriber({
  subscription_plan_id: plan.id,
  customer_email: "[email protected]",
  success_return_url: "https://example.com/welcome",
});
await bitxpay.subscriptions.listSubscribers({ plan_id: plan.id });
await bitxpay.subscriptions.getSubscriber(subscriber.id);
await bitxpay.subscriptions.updateSubscriber(subscriber.id, { status: "active" });

// Subscription links (the binding between a subscriber and a plan)
await bitxpay.subscriptions.listLinks({ is_active: true });
await bitxpay.subscriptions.getLink(linkId);

Subscription amounts are decimal strings (e.g. "10.00") to avoid floating-point precision loss.

Pagination

Every list method takes page / page_size and returns a normalized Page<T>, regardless of the (varying) envelope each endpoint uses on the wire:

const page = await bitxpay.payments.list({ page: 1, page_size: 50 });
page.data;        // T[] — the items on this page
page.page;        // current page number
page.pageSize;    // items per page
page.totalItems;  // total across all pages
page.totalPages;
page.hasMore;     // is there another page?

Iterate every page:

async function* allPaymentLinks() {
  let page = 1;
  while (true) {
    const result = await bitxpay.payments.list({ page, page_size: 100 });
    yield* result.data;
    if (!result.hasMore) break;
    page += 1;
  }
}

for await (const link of allPaymentLinks()) {
  console.log(link.id, link.payment_status);
}

Filters differ per endpoint (passed straight through to the API):

| List | Filters | | --- | --- | | payments.list | status, is_active, currency, min_amount, max_amount, created_from, created_to, search, sort_by, sort_order | | subscriptions.listPlans | deprecated, start_date, end_date, search | | subscriptions.listSubscribers | plan_id, start_date, end_date, search | | subscriptions.listLinks | is_active, plan_id, start_date, end_date, search |

Sorting (sort_by / sort_order) is supported only on payments.list.

Webhooks

BitXPay signs every webhook with X-Webhook-Signature: sha256=<hex> — an HMAC-SHA256 of the raw request body keyed by your webhook secret (set up under Developers → Webhooks in the dashboard).

Verify against the raw body bytes. If your framework parses JSON before you verify, the re-serialized bytes won't match and verification will fail. Use a raw-body parser on the webhook route.

Express

import express from "express";
import { constructWebhookEvent, WebhookSignatureError } from "@bitxpay/sdk";

const app = express();
const secret = process.env.BITXPAY_WEBHOOK_SECRET!;

// express.raw gives you the unparsed body Buffer needed for verification.
app.post("/webhooks/bitxpay", express.raw({ type: "*/*" }), (req, res) => {
  let event;
  try {
    event = constructWebhookEvent(req.body.toString("utf8"), req.headers, secret, {
      toleranceSeconds: 300, // reject deliveries older than 5 minutes (optional)
    });
  } catch (err) {
    if (err instanceof WebhookSignatureError) return res.status(400).send("invalid signature");
    throw err;
  }

  switch (event.event_type) {
    case "payment.succeeded":
      // fulfil the order — event.data has the payment details
      break;
    case "subscription.payment":
      // extend the subscription
      break;
  }

  res.sendStatus(200); // ack fast; do heavy work asynchronously
});

Next.js (App Router)

import { constructWebhookEvent, WebhookSignatureError } from "@bitxpay/sdk";

export async function POST(req: Request) {
  const raw = await req.text(); // raw body, unparsed
  try {
    const event = constructWebhookEvent(raw, req.headers, process.env.BITXPAY_WEBHOOK_SECRET!);
    // handle event.event_type ...
    return new Response("ok", { status: 200 });
  } catch (err) {
    if (err instanceof WebhookSignatureError) return new Response("invalid", { status: 400 });
    throw err;
  }
}

Just need a boolean check? Use verifyWebhookSignature(rawBody, signatureHeader, secret).

Event types: payment.succeeded, payment.failed, payment.pending, subscription.created, subscription.payment, subscription.failed, subscription.cancelled.

Error handling

Every non-2xx response throws a typed subclass of BitxpayAPIError:

| Class | Status | | --- | --- | | BitxpayBadRequestError | 400 | | BitxpayAuthenticationError | 401 | | BitxpayPermissionError | 403 | | BitxpayNotFoundError | 404 | | BitxpayConflictError | 409 | | BitxpayValidationError | 422 | | BitxpayRateLimitError | 429 (.retryAfter) | | BitxpayServerError | 5xx |

import {
  BitxpayAPIError,
  BitxpayValidationError,
  BitxpayRateLimitError,
} from "@bitxpay/sdk";

try {
  await bitxpay.payments.create({ payment_name: "x", amount: -1, currency: "USD" });
} catch (err) {
  if (err instanceof BitxpayValidationError) {
    console.error("Bad input:", err.message, err.body);
  } else if (err instanceof BitxpayRateLimitError) {
    console.error(`Rate limited; retry after ${err.retryAfter}s`);
  } else if (err instanceof BitxpayAPIError) {
    // status, machine code, and a request id for support tickets
    console.error(err.status, err.code, err.requestId, err.message);
  } else {
    throw err; // BitxpayConnectionError (network/timeout) or something unexpected
  }
}
  • BitxpayConnectionError — network failure or timeout (these are retried up to maxRetries first).
  • BitxpayConfigError — thrown from the constructor for bad/missing options, before any request.

Advanced configuration

// Point at a staging API
const staging = new BitxpayClient({
  apiKey,
  privateKey,
  baseUrl: "https://staging.example.com",
});

// Tighter timeout, no retries (e.g. inside a request handler with its own budget)
const strict = new BitxpayClient({ apiKey, privateKey, timeoutMs: 5_000, maxRetries: 0 });

// Bring your own fetch (proxy, logging, undici agent, ...)
import { fetch as undiciFetch } from "undici";
const instrumented = new BitxpayClient({ apiKey, privateKey, fetch: undiciFetch as typeof fetch });

Development

npm install
npm run build      # bundle to dist/ (ESM + CJS + .d.ts)
npm test           # vitest
npm run typecheck

License

MIT