Skip to content

fintech-sdk/clearbank-sdk

Repository files navigation

clearbank-sdk

Production-grade TypeScript SDK for the ClearBank UK API

npm version TypeScript License: MIT

Features

  • Full API coverage — GBP Accounts, FPS/CHAPS/Bacs/Cheques/CoP Payments, Multi-currency & FX, Embedded Banking, Webhooks
  • TypeScript-first — Complete type definitions for every request, response, and webhook event
  • Dual CJS/ESM output — Works in Node.js, Bun, Deno, and modern bundlers
  • RSA-SHA256 signing — Automatic DigitalSignature header using Web Crypto API (Node 18+)
  • Idempotent retries — Exponential backoff with full jitter; pinnable X-Request-Id for safe mutation retries
  • Telemetry hooks — Emit structured events to any observability provider
  • Zero mandatory dependencies — Uses native fetch and Web Crypto

Installation

npm install clearbank-sdk
# or
pnpm add clearbank-sdk
# or
yarn add clearbank-sdk

Requires Node.js ≥ 18 (for native fetch and crypto.subtle).

Quick Start

import { ClearBankClient } from 'clearbank-sdk';

const client = new ClearBankClient({
  apiToken: process.env.CLEARBANK_API_TOKEN!,
  privateKey: process.env.CLEARBANK_PRIVATE_KEY!,  // PEM string
  environment: 'simulation',                        // or 'production'
});

// List GBP accounts
const { data: accounts } = await client.accounts.list({ pageNumber: 1, pageSize: 20 });

// Send a Faster Payment
await client.payments.sendFPS({
  accountId: 'your-account-id',
  payment: {
    amount: '250.00',
    destinationSortCode: '040004',
    destinationAccountNumber: '12345678',
    destinationAccountName: 'Jane Smith',
    reference: 'Invoice 001',
  },
});

// Execute an FX trade
const trade = await client.multiCurrency.executeFXTrade({
  sellAccountId: 'gbp-account-id',
  buyAccountId: 'eur-account-id',
  sellCurrency: 'GBP',
  buyCurrency: 'EUR',
  sellAmount: '10000.00',
});
console.log(`Traded at rate: ${trade.rate}`);

Configuration

Option Type Default Description
apiToken string required Bearer token from ClearBank Portal
privateKey string RSA private key PEM (required for POST/PUT/PATCH)
environment 'simulation' | 'production' 'simulation' API environment
baseUrl string Override base URL (useful for testing)
timeoutMs number 30000 Request timeout in milliseconds
maxRetries number 3 Max retry attempts on 429/500/503
retryBaseDelayMs number 1000 Backoff base delay in milliseconds
telemetryHook TelemetryHook Called after every HTTP request

Error Handling

All API errors are thrown as ClearBankError:

import { ClearBankError } from 'clearbank-sdk';

try {
  await client.accounts.get('nonexistent-id');
} catch (err) {
  if (err instanceof ClearBankError) {
    console.log(err.statusCode);      // 404
    console.log(err.message);         // "Not Found"
    console.log(err.correlationId);   // X-Correlation-Id for support
    console.log(err.isNotFound());    // true
    console.log(err.isRetryable());   // false
  }
}

ClearBankError methods

Method Returns Description
isNotFound() boolean HTTP 404
isConflict() boolean HTTP 409 (duplicate X-Request-Id)
isRateLimited() boolean HTTP 429
isUnprocessable() boolean HTTP 422
isRetryable() boolean HTTP 429, 500, or 503

Idempotent Retries

ClearBank requires mutating requests (POST/PUT/PATCH) that fail with 5XX to be retried with the same X-Request-Id:

const requestId = ClearBankClient.generateRequestId();

try {
  await client.payments.sendFPS(params, { requestId });
} catch (err) {
  if (err instanceof ClearBankError && err.isRetryable()) {
    // Retry with identical requestId — ClearBank deduplicates on their side
    await client.payments.sendFPS(params, { requestId });
  }
}

Webhooks

import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhooks', async (req, res) => {
  // 1. Verify signature (using ClearBank's public key from Portal)
  const rawBody = JSON.stringify(req.body);
  const isValid = await client.webhooks.verifySignature(
    rawBody,
    req.headers['digitalsignature'] as string,
    process.env.CLEARBANK_PUBLIC_KEY!,
  );
  if (!isValid) return res.status(401).json({ error: 'Invalid signature' });

  // 2. Parse and dispatch
  const envelope = client.webhooks.parseEnvelope(req.body);
  await client.webhooks.dispatch(envelope, {
    TransactionSettled: async (event) => {
      console.log(`Payment settled: £${event.amount} on account ${event.accountId}`);
      await yourDatabase.recordSettlement(event);
    },
    FxTradeSettled: async (event) => {
      console.log(`FX trade ${event.tradeId} settled`);
    },
    CustomerKycStatusChanged: async (event) => {
      if (event.newStatus === 'Approved') {
        await activateCustomerAccount(event.customerId);
      }
    },
    onUnknown: (envelope) => {
      console.log('Unknown webhook event:', envelope.Type);
    },
  });

  // 3. Acknowledge
  res.json(client.webhooks.buildAck(envelope));
});

Domain Services

client.accounts — GBP Accounts

// Real accounts
await client.accounts.list({ pageNumber: 1, pageSize: 50 });
await client.accounts.get(accountId);
await client.accounts.create({ accountName: 'Segregated Pool', accountType: 'SegregatedPooled' });
await client.accounts.update(accountId, { accountName: 'Renamed', copEnabled: true });

// Virtual accounts
await client.accounts.listVirtual(accountId);
await client.accounts.createVirtual(accountId, { accountName: 'Customer 1', owner: customerId });
await client.accounts.getVirtual(accountId, virtualAccountId);

// Transactions
await client.accounts.listAllTransactions({ startDate: '2024-01-01', endDate: '2024-01-31' });
await client.accounts.listTransactions(accountId, { pageSize: 100 });
await client.accounts.getTransaction(accountId, transactionId);

// Bacs DDIs
await client.accounts.createDDI(accountId, { serviceUserNumber, reference, payerName, payerSortCode, payerAccountNumber });
await client.accounts.listDDIs(accountId);
await client.accounts.cancelDDI(accountId, mandateId);

// camt.053 statements
const messageId = await client.accounts.requestStatement({ accountId, startDate, endDate });
const allPages = await client.accounts.getAllStatementPages(messageId);

client.payments — GBP Payments

// Faster Payments
await client.payments.sendFPS({ accountId, payment: { amount, destinationSortCode, ... } });
await client.payments.sendFPSBulk({ accountId, payments: [...] });

// CHAPS
await client.payments.sendCHAPS({ debtorAccountId, amount, creditorName, creditorAddress: { country: 'GB' }, ... });
await client.payments.returnCHAPS({ originalInstructionId, debtorAccountId, returnReasonCode, amount });

// Internal transfers
await client.payments.sendInternalTransfer({ debtorAccountId, creditorAccountId, amount });
await client.payments.sendBulkInternalTransfer([...transfers]);

// Bacs returns
await client.payments.returnBacs(accountId, { transactionId, reasonCode });

// Cheques
await client.payments.submitChequeDeposit({ accountId, amount, chequeImageFront, chequeImageBack, micrLine });

// Confirmation of Payee
const result = await client.payments.checkCoP({ accountName, sortCode, accountNumber });
// result.matchResult: 'MATC' | 'CLOSE' | 'NOMATCH' | 'INAM' | 'PANM'
await client.payments.optOutAccountCoP(accountId);

// SEPA SCT UK
await client.payments.sendSEPA({ debtorAccountId, amount, creditorName, creditorIban, creditorBic });

client.multiCurrency — Multi-currency & FX

// Accounts
await client.multiCurrency.listAccounts('EUR');
await client.multiCurrency.createAccount({ accountName, accountType: 'YourFunds', currency: 'EUR' });

// International payments
await client.multiCurrency.sendPayment({ accountId, amount, currency, creditorName, creditorIban, creditorBic });
await client.multiCurrency.sendBulkPayments([...payments]);

// FX Spot
const trade = await client.multiCurrency.executeFXTrade({ sellAccountId, buyAccountId, sellCurrency, buyCurrency, sellAmount });

// FX RFQ
const quote = await client.multiCurrency.requestFXQuote({ sellAccountId, buyAccountId, ... });
if (!quote.isExpired()) {
  const trade = await client.multiCurrency.executeFXQuote(quote.quoteId);
}

client.embedded — Embedded Banking

// Customers
await client.embedded.createRetailCustomer({ firstName, lastName, dateOfBirth });
await client.embedded.createSoleTraderCustomer({ firstName, lastName, dateOfBirth, tradingName });
await client.embedded.createLegalEntityCustomer({ companyName, registrationNumber, registeredCountry, companyType });

// Accounts
await client.embedded.createPaymentAccount({ customerId, accountName });
await client.embedded.createSavingsAccount({ customerId, accountName });
await client.embedded.createISA({ customerId, accountName });
await client.embedded.sendFPS({ accountId, amount, destinationSortCode, destinationAccountNumber, destinationName });

// KYC
await client.embedded.submitKYC(customerId, { idDocumentType, idDocumentNumber, idDocumentExpiry, idDocumentCountry });
const status = await client.embedded.getKYCStatus(customerId);

// Interest
const products = await client.embedded.listInterestProducts();
await client.embedded.configureInterest(accountId, { productId });

Telemetry

const client = new ClearBankClient({
  apiToken: '...',
  telemetryHook: (event) => {
    yourMetrics.histogram('clearbank.request.duration', event.durationMs, {
      method: event.method,
      statusCode: String(event.statusCode ?? 'network_error'),
    });
    if (event.error) {
      yourLogger.error('ClearBank request failed', { error: event.error, requestId: event.requestId });
    }
  },
});

License

MIT © Kanishka Naik

Packages

 
 
 

Contributors