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

revolut-client

v1.0.0

Published

Production-grade TypeScript SDK for the complete Revolut Developer API

Readme

revolut-client

Production-grade TypeScript SDK for the complete Revolut Developer API platform.

npm TypeScript Tests Zero dependencies License: MIT


Features

  • Complete API coverage — Merchant, Business, Open Banking, Crypto Ramp, Crypto Exchange
  • Zero runtime dependencies — pure TypeScript, works in Node.js 18+, Deno, Cloudflare Workers, Bun
  • Dual CJS + ESM — tree-shakeable, works in every bundler
  • Branded primitivesCurrency, Amount, UUID prevent type confusion at compile time
  • Typed webhook dispatchhandler.on("ORDER_COMPLETED", (evt) => ...) narrows payload type automatically
  • Retry + jitter — exponential backoff with full jitter, configurable per-client
  • Token-bucket rate limiting — client-side, no external deps
  • Telemetry hooks — plug in your own logger/metrics/tracing
  • Replay-attack protection — optional 5-minute timestamp window on webhook handler
  • strictest TypeScriptexactOptionalPropertyTypes, noUncheckedIndexedAccess, private #fields

Installation

npm install revolut-client

Quick start

import { RevolutSDK, Currency, Amount } from "revolut-client";

const sdk = new RevolutSDK({
  merchantKey:  "sk_live_...",
  businessKey:  "biz_access_token",
  environment:  "prod",
  timeoutMs:    30_000,
});

// Create a payment order
const order = await sdk.merchant.createOrder({
  amount:      Amount(1000),  // £10.00 in minor units
  currency:    Currency("GBP"),
  description: "Widget purchase",
  capture_mode: "automatic",
});

console.log(order.checkout_url); // redirect customer here
console.log(order.token);        // for Revolut Checkout Widget

Sandbox

const sdk = new RevolutSDK({
  merchantKey: "sk_sandbox_...",
  environment: "sandbox",
});

APIs

Merchant API

import { MerchantClient, Currency, Amount } from "revolut-client/merchant";
// or via the unified SDK:
const m = sdk.merchant;

// Orders
const order    = await m.createOrder({ amount: Amount(2000), currency: Currency("EUR") });
const fetched  = await m.getOrder(order.id);
const list     = await m.listOrders({ page: { limit: 50 } });
const captured = await m.captureOrder(order.id, { amount: Amount(1500), currency: Currency("EUR") });
await m.cancelOrder(order.id);
await m.refundOrder(order.id, { amount: Amount(500) });

// Incremental authorisation (pre-auth orders, e.g. hotel holds)
await m.incrementalAuthorise(order.id, {
  amount:    Amount(15000),  // new total — not a delta
  currency:  Currency("GBP"),
  reference: "invoice_123",
});

// Pay via saved customer payment method (MIT)
await m.payViaSavedMethod(order.id, { saved_payment_method_id: savedMethodId });

// Update order before capture
await m.updateOrder(order.id, {
  description:    "Updated description",
  line_items:     [{ name: "Widget", total_amount: Amount(2000) }],
  industry_data:  { airline: { legs: [...] } },
});

// Full payment lifecycle tracking
const details = await m.getPaymentDetails(paymentId);
console.log(details.state);          // "authorised" | "captured" | ...
console.log(details.fingerprint);    // unique 44-char ID for duplicate detection
console.log(details.failure_reason); // "insufficient_funds" | "expired_card" | ...

// Customers + saved payment methods
const customer = await m.createCustomer({ email: "[email protected]" });
const methods  = await m.listSavedPaymentMethods(customer.id);

// Subscription Plans (Variations + Phases model)
const plan = await m.createPlan({
  name: "Pro Plan",
  trial_duration: "P14D",  // 14-day free trial
  variations: [
    {
      name: "Monthly",
      phases: [
        { ordinal: 1, cycle_duration: "P1M", cycle_count: 1, amount: Amount(0),   currency: Currency("GBP") }, // trial
        { ordinal: 2, cycle_duration: "P1M",                 amount: Amount(999), currency: Currency("GBP") }, // £9.99/mo
      ],
    },
    {
      name: "Yearly",
      phases: [
        { ordinal: 1, cycle_duration: "P1Y", amount: Amount(9900), currency: Currency("GBP") },
      ],
    },
  ],
});

// Subscriptions
const sub = await m.createSubscription({
  plan_variation_id:       plan.variations[0].id,
  customer_id:             customer.id,
  setup_order_redirect_url: "https://example.com/thanks",
});
// sub.setup_order_id → use getOrder(sub.setup_order_id) to get checkout_url
const cycles = await m.listBillingCycles(sub.id);

// Disputes
await m.acceptDispute(disputeId);
const evidence = await m.uploadDisputeEvidence(disputeId, {
  file_name:    "invoice.pdf",
  content_type: "application/pdf",
});
await m.challengeDispute(disputeId, { evidence_ids: [evidence.id] });

// Report Runs (async CSV generation)
const report = await m.createReportRun({
  type:      "transactions",
  date_from: "2025-01-01",
  date_to:   "2025-01-31",
});
const ready = await m.getReportRun(report.id);
console.log(ready.download_url); // signed S3 URL

// Fast Checkout address validation (Revolut Pay)
const syncWebhook = await m.registerAddressValidation({
  url: "https://your-backend.com/validate-address",
});
console.log(syncWebhook.signing_key); // use to verify Revolut-Pay-Payload-Signature

Business API

const b = sdk.business;

// Accounts
const accounts = await b.listAccounts();
const details  = await b.getAccountBankDetails(accounts[0].id); // IBAN, sort code, BIC

// Cards
const cards = await b.listCards();
await b.freezeCard(cardId);
await b.unfreezeCard(cardId);
await b.terminateCard(cardId); // irreversible

// Counterparties + UK CoP
const cp  = await b.addCounterparty({ profile_type: "business", name: "Acme Ltd" });
const cop = await b.validatePayeeName({ name: "Acme Ltd", sort_code: "608371", account_no: "12345678" });

// Payments & Transfers
const tx1 = await b.createTransfer({
  request_id:        "uuid-v4",
  source_account_id: gbpAccountId,
  target_account_id: usdAccountId,
  amount:            1000,
  currency:          Currency("GBP"),
});
const tx2 = await b.createPayment({
  request_id:   "uuid-v4",
  account_id:   gbpAccountId,
  counterparty: cpId,
  amount:       500,
  currency:     Currency("GBP"),
  reference:    "Invoice #123",
});

// FX
const rate = await b.getExchangeRate(Currency("USD"), Currency("GBP"));
const fx   = await b.exchange({
  request_id: "uuid-v4",
  from: { account_id: usdAccId, currency: Currency("USD"), amount: 1000 },
  to:   { account_id: gbpAccId, currency: Currency("GBP") },
});

// Webhooks v2 (recommended)
const wh = await b.createWebhookV2({
  url:    "https://your-app.com/revolut/business",
  events: ["TransactionCreated", "TransactionStateChanged"],
});
const rotated = await b.rotateWebhookSigningSecretV2(wh.id, {
  expiration_period: "P1D", // old secret valid for 1 day
});
const failed = await b.getFailedWebhookEvents(wh.id, { limit: 20 });

Webhooks

import { WebhookHandler, computeWebhookSignature } from "revolut-client/webhook";

const handler = new WebhookHandler({
  secret:            "wsk_...",      // from Revolut dashboard
  validateTimestamp: true,           // reject events > 5 minutes old (anti-replay)
  onError: (err, rawBody) => {
    logger.error({ err, rawBody }, "webhook handler error");
  },
});

// Strongly-typed handlers — payload type is narrowed by event name
handler
  .on("ORDER_COMPLETED", async (evt) => {
    // evt.order_id is typed as string
    await fulfillOrder(evt.order_id);
  })
  .on("ORDER_INCREMENTAL_AUTHORISATION_AUTHORISED", async (evt) => {
    await handleIncrementalAuth(evt.order_id, evt.incremental_authorisation_reference);
  })
  .on("DISPUTE_ACTION_REQUIRED", async (evt) => {
    await notifyDisputeTeam(evt.dispute_id);
  })
  .on("SUBSCRIPTION_INITIATED", async (evt) => {
    await activateSubscription(evt.subscription_id);
  })
  .on("PAYOUT_COMPLETED", async (evt) => {
    await reconcilePayout(evt.payout_id);
  });

// In your HTTP server (Express / Fastify / Hono / any framework)
app.post("/webhooks/revolut", async (req, res) => {
  await handler.processRequest(req.body, req.headers);
  res.sendStatus(200);
});

Revolut's signature format: HMAC-SHA256("v1.{Revolut-Request-Timestamp}.{rawBody}")v1={hex}


Error handling

import {
  isAPIError, isValidationError, isNetworkError,
  APIError, ValidationError
} from "revolut-client";

try {
  const order = await sdk.merchant.getOrder(orderId);
} catch (err) {
  if (isValidationError(err)) {
    // Caught before HTTP — bad input
    console.error(`Field "${err.field}": ${err.message}`);
  } else if (isAPIError(err)) {
    if (err.isNotFound)     console.error("Order not found");
    if (err.isRateLimited)  console.warn("Rate limited — back off");
    if (err.isUnauthorized) console.error("Invalid API key");
    if (err.isRetryable)    console.warn("Transient server error");
    console.error(`HTTP ${err.statusCode} [${err.code}]: ${err.message}`);
    console.error(`request_id: ${err.requestId}`); // for Revolut support
  } else if (isNetworkError(err)) {
    console.error("Network failure:", err.message);
  }
}

Configuration

import {
  RevolutSDK,
  withApiKey, withSandbox, withRetry, withRateLimit, withTelemetry, withNoRetry
} from "revolut-client";

const sdk = new RevolutSDK({
  merchantKey: "sk_live_...",

  environment: "prod",  // or "sandbox"

  timeoutMs: 30_000,

  retry: {
    maxAttempts:    3,
    initialDelayMs: 500,
    maxDelayMs:     30_000,
    multiplier:     2,
    jitter:         0.5,
  },

  rateLimit: {
    requestsPerSecond: 50,
    burst:             100,
  },

  telemetry: {
    onRequest:  ({ method, url, attempt }) => logger.debug({ method, url, attempt }),
    onResponse: ({ statusCode, durationMs }) => metrics.histogram("http.duration", durationMs),
    onError:    ({ error, attempt, final }) => {
      if (final) logger.error({ error, attempt }, "request failed");
    },
  },
});

Pagination

import { hasNextPage } from "revolut-client";

let cursor: string | undefined;
do {
  const page = await sdk.merchant.listOrders({ page: { limit: 100, cursor } });
  for (const order of page.items) {
    await process(order);
  }
  cursor = page.next_cursor;
} while (cursor && hasNextPage(page));

Sub-package imports

// Full SDK
import { RevolutSDK } from "revolut-client";

// Individual client + types
import { MerchantClient }     from "revolut-client/merchant";
import { BusinessClient }     from "revolut-client/business";
import { OpenBankingClient }  from "revolut-client/openbanking";
import { CryptoRampClient }   from "revolut-client/cryptoramp";
import { CryptoExchangeClient } from "revolut-client/cryptoexchange";
import { WebhookHandler }     from "revolut-client/webhook";

Testing

npm test              # run all 148 tests
npm run test:coverage # coverage report (target: 80%+)
npm run test:watch    # interactive watch mode
npm run typecheck     # TypeScript strict check, zero errors
npm run build         # dual CJS + ESM dist/

License

MIT — see LICENSE