clearbank-sdk
v1.0.0
Published
Production-grade TypeScript SDK for the ClearBank UK API. Covers GBP Accounts, Payments (FPS, CHAPS, Bacs, Cheques, CoP), Multi-currency & FX, Embedded Banking, and Webhooks.
Maintainers
Readme
clearbank-sdk
Production-grade TypeScript SDK for the ClearBank UK API
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
DigitalSignatureheader using Web Crypto API (Node 18+) - Idempotent retries — Exponential backoff with full jitter; pinnable
X-Request-Idfor safe mutation retries - Telemetry hooks — Emit structured events to any observability provider
- Zero mandatory dependencies — Uses native
fetchand Web Crypto
Installation
npm install clearbank-sdk
# or
pnpm add clearbank-sdk
# or
yarn add clearbank-sdkRequires 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
