ClearBank.Net 1.2.0

dotnet add package ClearBank.Net --version 1.2.0
                    
NuGet\Install-Package ClearBank.Net -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ClearBank.Net" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ClearBank.Net" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="ClearBank.Net" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ClearBank.Net --version 1.2.0
                    
#r "nuget: ClearBank.Net, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ClearBank.Net@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ClearBank.Net&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=ClearBank.Net&version=1.2.0
                    
Install as a Cake Tool

ClearBank.NET

Unofficial .NET client for ClearBank integration, creating online payments via their API. Bank payment handling automation in the United Kingdom (UK/GB) and European Union (EU). Released as a NuGet package. This aims to be bare and easy.

The library has been structured as UK/UK.Multicurrency/EU, and aims to cover major use cases from ClearBank developer portal with simple setup in both Microsoft DotNet and Microsoft .NET Framework.

Here are the pre-conditions:

1. Create Azure KeyVault

From the Access Policies, add the role, and "Key Permissions" for the Sign & Verify. Azure KeyVault supports HSM (hardware security module) backed keys.

2. Configure the Certificate -tab

For example, a new self-signed certificate can be used, but the PEM format has to be selected.

Download the certificate, a PEM-file.

3. Create a CSR with Open-SSL and upload it to the ClearBank portal

openssl.exe req -new -sha256 -key "c:\temp\downloaded.pem" -out file.csr
  • Upload that to the portal https://institution-sim.clearbank.co.uk/
  • Copy the private key (the long string) from the message box. You need that in the config later, as that will be used in the POST header Authorization: Bearer (the long string)

Update: You can also get CSR now directly from Azure keyvault, by selecting the certificate and clicking "Certificate Operation" → "Download CSR"

(4. Optional: Ensure the correctness with FI-API-Signtool)

The signing is done similarly to this repo, but the official code is more messy because it's C#. GitHub - clearbank/fi-api-signtool

5. Start using this library

Your user code should look something like this:


let doSomeTransactions =
    let clearbankDefaultConfig =
        {
            BaseUrl = "https://institution-api-sim.clearbank.co.uk/"
            PrivateKey = "..."
            AzureKeyVaultName =  "myVault"
            AzureKeyVaultCredentials = DefaultCredentials
        } : ClearBank.Common.ClearbankConfiguration

    let azureKeyVaultCertificateName = "my-cert"
    let fromAccount = ClearBank.Common.UK_Domestic("60-01-34", "51112345")

    let target1 = 
        ClearBank.UK.createCreditTransfer
            {
                To = ClearBank.Common.UK_Domestic("20-20-15", "55555555")
                AccountHolder = "Mr Test"
                Sum = 123.00m
                Currency = "GBP"
                Description = "Phone Bill"
                PaymentReference = "123456789"
                TransactionId = "12345" // End-to-end: You identify corresponding webhooks with this.
            }

    let target2 = 
        ClearBank.UK.createCreditTransfer
            {
                To = ClearBank.Common.UK_Domestic("40-47-84", "70872490")
                AccountHolder = "John Doe"
                Sum = 123.00m
                Currency = "GBP"
                Description = "Some money"
                PaymentReference = "12345"
                TransactionId = "12345"
            }

    let xReqId = Guid.NewGuid()
    let instructions = ClearBank.UK.createPaymentInstruction "Batch123" fromAccount  [| target1 ; target2 |]
    ClearBank.UK.transferPayments clearbankDefaultConfig azureKeyVaultCertificateName xReqId [| instructions |] |> Async.RunSynchronously

If you have problems with the KeyVault authentication, you can change the AzureKeyVaultCredentials in the config

    let clearbankDefaultConfig =
        {
            BaseUrl = "https://institution-api-sim.clearbank.co.uk/"
            PrivateKey = "..."
            AzureKeyVaultName =  "..."
            AzureKeyVaultCertificateName = "..."
            AzureKeyVaultCredentials =
                CredentialsWithOptions (
                    Azure.Identity.DefaultAzureCredentialOptions (
                        //ExcludeEnvironmentCredential = true
                        //,ExcludeManagedIdentityCredential = true
                        ExcludeSharedTokenCacheCredential = true
                        ,ExcludeVisualStudioCredential = true
                        //,ExcludeVisualStudioCodeCredential = true
                        //,ExcludeAzureCliCredential = true
                        //,ExcludeInteractiveBrowserCredential = true
                    )) 
            LogUnsuccessfulHandler = None
        } : ClearBank.Common.ClearbankConfiguration

The last LogUnsuccessfulHandler property is an optional error-logging callback. You could replace it e.g. with Some logging and have a function:

    let logging(status,content) =
        match ClearBank.Common.parseClearBankErrorContent content with
        | ClearBankEmptyResponse -> Console.WriteLine "Response was empty"
        | ClearBankTransactionError errors -> errors |> Seq.iter(fun (tid,err) -> Console.WriteLine("Transaction id " + tid + " failed for " + err))
        | ClearBankGeneralError(title, detail) -> Console.WriteLine(title + ", " + detail)
        | ClearBankUnknownError content -> Console.WriteLine("JSON: " + content)

Creating accounts

There is a method ClearBank.UK.createNewAccount to create new accounts.

Getting accounts and transactions

There are methods ClearBank.UK.getAccounts, that you can use for e.g. getting balances, and ClearBank.UK.getTransactions config pageSize pageNumber startDate endDate

Shared client, pluggable signing and the Task API

The functions above take the configuration and create an HttpClient per call, signing with Azure Key Vault. For a server, create one ClearBankClient for the application's lifetime and use the Api modules (ClearBank.UK.Api, ClearBank.UK.MultiCurrency.Api, ClearBank.EU.Api). They return Task<Result<'T, exn * string>>, take a CancellationToken, and share the caller's HttpClient. Set AllowAutoRedirect = false on your own client's handler, as ClearBankClient.createHttpClient does: following a redirect would send signatures and payment bodies to whatever host it points at.

open System
open ClearBank.Common
open ClearBank.UK

// DigitalSignature: RSASSA-PKCS1-v1_5 with SHA-256 over the UTF-8 body. Pick a signer:
let signer = RequestSigner.azureKeyVault DefaultCredentials "myVault" "my-cert"
// let signer = RequestSigner.rsa rsaPrivateKey            // a local System.Security.Cryptography.RSA
// let signer = RequestSigner.ofDigest signDigestWithKms   // a remote key that signs a SHA-256 digest

// Either your own HttpClient with BaseAddress set (e.g. from IHttpClientFactory)...
let client = ClearBankClient.create httpClient "the API token" signer
// ...or one built from the configuration, with its LogUnsuccessfulHandler:
// let client = ClearBankClient.fromConfig clearbankDefaultConfig signer

task {
    let! accounts = Api.getAccountsAsync client ValueNone ValueNone ct

    let! payee =
        Api.verifyPayeeAsync client (Guid.NewGuid())
            { SortCode = "20-20-15"; AccountNumber = "55555555"; OwnerName = ",,John Doe"
              LegalOwnerType = LegalOwnerType.Personal; SecondaryReference = ValueNone; EndToEndId = ValueNone } ct

    match payee with
    | Ok check when check.Matched -> () // pay with Api.transferPaymentsAsync
    | Ok check -> () // show check.Name / check.ReasonCode to the payer before paying
    | Error (err, details) -> ()
}

AWS KMS needs no AWS dependency in this library; sign the digest in your own code:

let kmsSigner (kms: Amazon.KeyManagementService.IAmazonKeyManagementService) keyId =
    RequestSigner.ofDigest (fun digest ct ->
        task {
            let! response =
                kms.SignAsync(
                    Amazon.KeyManagementService.Model.SignRequest(
                        KeyId = keyId,
                        Message = new IO.MemoryStream(digest),
                        MessageType = Amazon.KeyManagementService.MessageType.DIGEST,
                        SigningAlgorithm = Amazon.KeyManagementService.SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_256), ct)
            return response.Signature.ToArray()
        })

From C#: RequestSigner.FromRsa(rsa), RequestSigner.FromDigest((digest, ct) => ...), RequestSigner.Create((body, ct) => ...), ClearBankClient.Create(httpClient, token, signer), then e.g. await UK.Api.getAccountsAsync(client, FSharpValueOption<int>.None, FSharpValueOption<int>.None, ct).

ClearBank.UK.Api operations (real accounts and, through AccountRef.Virtual, virtual accounts):

Area Functions
Accounts createAccountAsync, getAccountsAsync, getEveryAccountAsync (every page), getAccountAsync, updateAccountAsync (status, reason, owner, minimum balance), closeAccountAsync
Interest-bearing account closure requestAccountClosureAsync (asynchronous; interest goes to the nominated account), getAccountClosureAsync
Transactions getTransactionsAsync, getEveryTransactionAsync and getEveryAccountTransactionAsync (every page), getAccountTransactionsAsync, getAccountTransactionAsync, returnTransactionsAsync (Bacs returns)
Bacs getUnpaidDirectDebitsAsync, getUnappliedDirectCreditsAsync
Virtual accounts createVirtualAccountsAsync, getVirtualAccountsAsync, getAllVirtualAccountsAsync, getVirtualAccountAsync, updateVirtualAccountAsync, disableVirtualAccountAsync
Direct Debit mandates getMandatesAsync, cancelMandateAsync, returnMandateAsync, getMandateCollectionsAsync, getUnpaidCollectionsAsync
Payments transferPaymentsAsync (Faster Payments; payments to accounts held at ClearBank settle as internal transfers)
CHAPS chapsCustomerPaymentAsync, chapsInstitutionPaymentAsync, chapsReturnPaymentAsync (v6; build requests with ChapsV6 types and accounts with UK.chapsAccount)
Confirmation of Payee verifyPayeeAsync, requiresSecondaryReferenceAsync, setCopParticipationAsync (inbound opt-out)
Test testAsync

ClearBank.UK.MultiCurrency.Api also has cancelPaymentsAsync, and ClearBank.EU.Api covers the SEPA and Target2 calls. Each requestId becomes the X-Request-Id header: use a new one per request and keep it with your record of the request.

Not covered yet: FPS returns and single payments, statements, embedded banking (customers, KYC, ISAs, savings) and multi-currency virtual accounts.

The schemas in Schemas/ are copies of ClearBank's published endpoints. Local edits: clearbank-api-v1.json and clearbank-api-v2.json have response keys trimmed and a malformed examples and query uniqueItems removed for the strict OpenAPI reader; chaps-v6.json and mccy-initiate-payment-v1-dec21.json declare the payment amount as double, because SwaggerProvider maps other number formats to float32.

Webhook responses

To receive webhooks, you need a web server, which is outside the scope of this library. However, there are some helper classes provided in this library.

To use those, your server code could look something like this:

type CBWebhookController() as this =
    inherit ApiController()

    member __.Post ()
        async {
            // 1. Verify the webhook against your ClearBank public key:

            // Download the public key (a .pem file) from your ClearBank portal and use a converter such as https://raskeyconverter.azurewebsites.net/PemToXml to convert the text to XML
            let publicKeyXml = "<RSAKeyValue>...</RSAKeyValue>"

            let signature = this.Request.Headers.GetValues("DigitalSignature") |> Seq.tryHead |> Option.map Convert.FromBase64String //add some error handling
            let! bodyJson = this.Request.Content.ReadAsStringAsync() |> Async.AwaitTask

            let! isVerified = ClearBank.Common.verifySignature publicKeyXml signature bodyJson //proceed only if true

            // 2. Parse and handle the request:
            let parsed = ClearBank.Webhooks.parsePaymentsCallUK bodyJson

            // Use parsed.Type to get the webhook type.
            // Different types may have the corresponding end-to-end transactionId in different places.
            // Fetch your transaction based on that ID, and do whatever you want.

            //    match ClearBank.Webhooks.webhookTypeUK parsed with
            //    | ClearBank.Webhooks.UkWebhook.TransactionSettled -> ...
            //    | ClearBank.Webhooks.UkWebhook.PaymentMessageAssessmentFailed -> ...
            //    | ClearBank.Webhooks.UkWebhook.PaymentMessageValidationFailed -> ...
            //    | ClearBank.Webhooks.UkWebhook.TransactionRejected -> ...
            //    | ClearBank.Webhooks.UkWebhook.BacsUnpaidDirectDebitReceived -> ...
            //    | ClearBank.Webhooks.UkWebhook.Unlisted other -> ...
            //    | _ -> (* FITestEvent *) ...
            // The string constants in ClearBank.Webhooks.WebhookTypes still work with parsed.Type.

            // 3. Create response (or Webhooks.createSignedResponseAsync signer this.Request parsed.Nonce ct with any RequestSigner)
            return! ClearBank.Webhooks.createResponse clearbankDefaultConfig azureKeyVaultCertificateName this.Request parsed.Nonce

        } |> Async.StartAsTask

To test webhooks, you can use e.g. Fiddler to compose them and https://webhook.site/ to get their calls.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 0 11/7/2026
1.1.2 48 11/5/2026
1.1.1 51 11/4/2026
1.1.0 108 10/27/2026
1.0.37 268 4/30/2026
1.0.35 297 11/2/2025
1.0.34 275 10/27/2025
1.0.33 405 5/8/2025
1.0.32 630 3/12/2025
1.0.31 641 1/24/2025
1.0.29 342 1/21/2025
1.0.28 330 1/16/2025
1.0.27 332 1/14/2025
1.0.26 872 10/7/2024
1.0.25 799 7/14/2024
1.0.24 1,228 3/13/2024
1.0.23 359 3/12/2024
1.0.22 809 1/10/2024
1.0.21 424 1/3/2024
Loading failed

v1.2.0:
- FIXED: the token reaches the HTTP call of every UK v1 and v2 operation, so cancelling stops it
- FIXED: cancelling a configuration-based call ends Canceled with OperationCanceledException, not Faulted; a timeout stays an Error
- FIXED: printing ClearbankConfiguration or ClearBankClient (%A, ToString) hides the API token
- FIXED: a webhook nonce above the Int32 range no longer overflows the response or the EU parse
- BREAKING: WebHookResponse.Nonce and the EU webhook's Nonce are int64
v1.1.2:
- ADDED: Api.getEveryAccountAsync, getEveryTransactionAsync and getEveryAccountTransactionAsync read every page
v1.1.1:
- FIXED: the HttpClients made here no longer follow redirects, which sent signatures and payment bodies to the redirect's host; a 3xx answer is an error
v1.1.0:
- BREAKING: SwaggerProvider 4.2.1: generated models take and return option for optional strings, Guid for ids, DateTimeOffset for date-times; multi-currency AccountIdentifier kind is an enum
- CHANGED: FSharp.Core 10.1.401, FSharp.Data 8.2.0, KeyVaultSigning 1.0.13 (async signing-client cache fix)
- CHANGED: legacy DateTime filters of unspecified kind are sent as UTC
- FIXED: bundled v1/v2 schemas: response keys with spaces, a non-map examples and query uniqueItems that the OpenAPI reader rejects
- ADDED: CHAPS v6 customer, institution and return payments (chapsCustomerPaymentAsync, chapsInstitutionPaymentAsync, chapsReturnPaymentAsync, chapsAccount)
- ADDED: interest-bearing account closure: requestAccountClosureAsync, getAccountClosureAsync
- ADDED: bundled sterling-v1, cop-outbound-v1, cop-accounts-v1 and chaps-v6 schemas; chaps-v6 amount as double
- FIXED: payment amounts were formatted with the current culture: under a decimal-comma culture (fi-FI, de-DE) every FPS, SEPA, T2, multi-currency and CHAPS payment failed
- FIXED: a request that cannot be serialized is an Error, not an exception
- FIXED: multi-currency payment amounts were float32 (1234567.89 was sent as 1234567.9); PaymentRequestItem amount is now float, sent with two decimals
- ADDED: RequestSigner: Azure Key Vault, local RSA key, or a SHA-256 digest delegate (AWS KMS) for DigitalSignature
- ADDED: ClearBankClient over a caller-owned, shared HttpClient; Task-based UK.Api, UK.MultiCurrency.Api and EU.Api with CancellationToken
- ADDED: UK accounts: get by id, update (status, reason, owner, minimum balance), close
- ADDED: UK virtual accounts: create, list, list all, get, update, disable
- ADDED: Bacs Direct Debit mandates: list, cancel, return, collections, unpaid collections; transaction returns; unpaid Direct Debits and unapplied Direct Credits
- ADDED: per-account and per-virtual-account transactions
- ADDED: Confirmation of Payee name verification, Secondary Reference Data check, inbound CoP opt-out
- ADDED: multi-currency payment cancellation
- ADDED: typed UkWebhook / EuWebhook with webhookTypeUK / webhookTypeEU; createSignedResponseAsync with any signer
- ADDED: ClearBankResponseException carries the status and body of non-success responses
- FIXED: multi-currency updateAccount, updateAccountStatus and addCurrency sent Authorization, DigitalSignature and X-Request-Id in the wrong headers
- FIXED: multi-currency deleteAccount sent no Authorization header
- CHANGED: the configuration-based functions delegate to the Task API and create their HttpClient when run, not when called