Skip to main content
The @your-org/checkout-sdk package is a fully typed TypeScript client for the Crypto Checkout API. It covers all public resources — payment links, checkout sessions, webhook endpoints, and events — and includes built-in webhook signature verification so you never have to hand-roll HMAC logic.

Installation

npm install @your-org/checkout-sdk

Initialization

Import CheckoutClient and pass your credentials. The client is safe to instantiate once and reuse across requests.
import { CheckoutClient } from "@your-org/checkout-sdk";

const client = new CheckoutClient({
  apiKey: process.env.CHECKOUT_API_KEY!,      // ck_live_... or ck_test_...
  baseUrl: "https://your-checkout-domain.com", // your self-hosted instance URL
});

ClientOptions

OptionRequiredDescription
apiKeyYour API key (ck_live_… for production, ck_test_… for test mode).
baseUrlNoThe root URL of your Crypto Checkout instance. Defaults to https://checkout.example.com. Always set this to your own deployment.
fetchNoA custom fetch implementation. Useful when polyfilling in older Node.js runtimes or in test environments.
The apiKey is the only required option. If you omit baseUrl the client falls back to the default placeholder — make sure you override it in production.

Resources

Payment links are reusable URLs that can accept unlimited payments. Create one per product, plan, or donation tier and share the checkoutUrl with customers.
// Create a payment link
const link = await client.paymentLinks.create({
  title: "Premium Plan",
  fiatAmount: "99.00",
  fiatCurrency: "USD",
  successUrl: "https://your-site.com/thanks",
});
console.log(link.checkoutUrl); // share this with customers

// List payment links (cursor-paginated)
const { data } = await client.paymentLinks.list({ limit: 10 });

// Retrieve a single link
const link = await client.paymentLinks.retrieve("pl_...");

// Update — e.g. deactivate
const updated = await client.paymentLinks.update("pl_...", { active: false });

// Delete permanently
await client.paymentLinks.del("pl_...");

Checkout Sessions

Checkout sessions are single-use payment intents. Create a session either from an existing payment link (linkId) or ad-hoc with an inline amount. Always redirect the customer to session.checkoutUrl immediately after creation.
// Link mode — amount is inherited from the payment link
const session = await client.checkoutSessions.create(
  {
    linkId: "pl_...",
    customerEmail: "[email protected]",
    metadata: { orderId: "ORD-42" },
  },
  { idempotencyKey: "order-42-v1" },
);

// Ad-hoc mode — supply amount directly
const session = await client.checkoutSessions.create(
  {
    amount: { value: "73.42", currency: "USD" },
    successUrl: "https://store.com/ok",
  },
  { idempotencyKey: "payment-abc-v1" },
);

console.log(session.checkoutUrl); // redirect the customer here
console.log(session.status);      // "pending"

// Retrieve by ID
const s = await client.checkoutSessions.retrieve("cs_...");

// List with filters
const paid = await client.checkoutSessions.list({ status: "paid", limit: 25 });
Always pass an idempotencyKey when creating sessions so that network retries don’t create duplicate payments. See Idempotency for the full key format rules.
Session status values
StatusMeaning
pendingWaiting for a blockchain transfer
detectedTransfer seen on-chain, awaiting confirmations
paidFully confirmed at the expected amount
underpaidPayment received but below the required amount
overpaidPayment received but above the required amount
paid_lateConfirmed after the session had expired
expiredTimer elapsed with no detected payment
failedTerminal failure

Webhook Endpoints

Register HTTPS URLs to receive real-time event notifications. The secret returned on creation is used to verify incoming payloads — save it immediately, it is only shown once.
// Create a webhook endpoint
const endpoint = await client.webhookEndpoints.create({
  url: "https://your-site.com/webhooks/crypto",
  events: ["session.paid", "session.expired"],
});
console.log(endpoint.secret); // save this — only shown once

// Retrieve (secret is NOT returned after creation)
const ep = await client.webhookEndpoints.retrieve(endpoint.id);

// List all endpoints
const { data: endpoints } = await client.webhookEndpoints.list();

// Send a test event to verify your handler
await client.webhookEndpoints.test(endpoint.id);

// Delete an endpoint
await client.webhookEndpoints.del(endpoint.id);
The webhook secret (prefixed whsec_…) is returned only in the create response. Store it in a secret manager or environment variable before the response leaves your server.

Events

Events are an immutable log of everything that happened in your account. You can fetch them individually, page through them, or use autoPaginate to stream all matching events without managing cursors.
// List the 20 most recent events
const { data: events } = await client.events.list({ limit: 20 });

// Retrieve a single event by ID
const event = await client.events.retrieve("evt_...");

// Auto-paginate — iterate over all paid events without managing cursors
for await (const event of client.events.list({
  type: "session.paid",
  autoPaginate: true,
})) {
  console.log(event.id, event.createdAt);
}

Error Handling

Every API error is thrown as a typed subclass of CheckoutError. Catch the most specific class you care about; all errors expose status, code, type, and an optional param field that names the invalid request parameter.
import {
  CheckoutError,
  AuthenticationError,
  PermissionError,
  InvalidRequestError,
  NotFoundError,
  ConflictError,
  RateLimitError,
  ServerError,
} from "@your-org/checkout-sdk";

try {
  const session = await client.checkoutSessions.create({ /* ... */ });
} catch (err) {
  if (err instanceof AuthenticationError) {
    // Invalid or missing API key — check your CHECKOUT_API_KEY env var
    console.error("Auth failed:", err.message);
  } else if (err instanceof InvalidRequestError) {
    // The request body had a validation problem
    console.error(`Invalid param "${err.param}":`, err.message);
  } else if (err instanceof NotFoundError) {
    // The resource ID doesn't exist or isn't accessible
    console.error("Not found:", err.message);
  } else if (err instanceof RateLimitError) {
    // Back off and retry after a short delay
    await sleep(1000);
  } else if (err instanceof CheckoutError) {
    // Catch-all for any other API error
    console.error(`[${err.status}] ${err.code}:`, err.message);
  } else {
    throw err; // re-throw non-SDK errors
  }
}
Error class reference
ClassHTTP statusWhen it’s thrown
AuthenticationError401Missing, malformed, or revoked API key
PermissionError403Key lacks the required scope
InvalidRequestError400Request body or parameters failed validation
NotFoundError404Resource doesn’t exist or isn’t accessible
ConflictError409Duplicate idempotent request with different parameters
RateLimitError429Too many requests — back off and retry
ServerError5xxUnexpected server-side error

Idempotency

Pass an idempotencyKey string in the second argument (RequestOptions) on any mutating call. The server replays the original response for up to 24 hours when it receives the same key, so retrying a failed network request never creates a duplicate.
const session = await client.checkoutSessions.create(
  { amount: { value: "25.00", currency: "USD" } },
  { idempotencyKey: `order-${orderId}-v1` },
);
Keys must be unique per logical operation. A good convention is <resource>-<externalId>-<attemptVersion>. See Idempotency for the full specification.

Webhook Verification

Use the verifyWebhook helper (shipped as a separate entry point) to validate the HMAC-SHA256 signature on every incoming webhook before trusting its payload.
import { verifyWebhook, WebhookVerificationError } from "@your-org/checkout-sdk/webhooks";

// In your HTTP handler (Express, Next.js Route Handler, etc.)
const rawBody = await req.text(); // must be the raw, unparsed body string

try {
  const event = verifyWebhook({
    payload: rawBody,
    signature: req.headers.get("x-webhook-signature") ?? "",
    secret: process.env.CHECKOUT_WEBHOOK_SECRET!,
    toleranceSeconds: 300, // default; reject events older than 5 minutes
  });

  switch (event.type) {
    case "session.paid":
      await fulfillOrder(event.data);
      break;
    // handle other event types...
  }
} catch (err) {
  if (err instanceof WebhookVerificationError) {
    return new Response("Unauthorized", { status: 401 });
  }
  throw err;
}
For the full signature algorithm and header format, see Webhook Signature Verification.