GoCardless Embed

Bank Details Access

The /bank_account_details endpoint allows you to access encrypted bank account details by bank account identifier.

This document outlines the steps required to retrieve customer bank account details. The process consists of two key steps:

  1. One-Time Public Key Setup: Generate your JWKS and add your public key URL in the GoCardless dashboard for encrypting the bank details. This setup is a one-time action.

  2. Retrieving Bank Account Details: Use the GoCardless API to retrieve encrypted bank account details and decrypt the response to get the customer's plaintext bank details.

Public Key Setup

The public key provided to GoCardless, must be an RSA 2048 bit key. This will be used to encrypt the symmetric encryption key using the OAEP padding scheme.

Please follow the steps outlined below to generate your JWKS and add your public key URL in the GoCardless dashboard. 

JWKS

What is a JWKS?

JWKS (JSON Web Key Set) is a JSON object that represents a set of cryptographic keys. A JWKS contains an array of JSON Web Keys (JWK), which are JSON objects that represent individual keys.

Generating your JWKS

  1. Generate an RSA-2048 private key

    openssl genrsa -out privatekey.pem 2048 openssl rsa -in privatekey.pem -pubout -out publickey.pem

  2. Securely store your private key

  3. Convert your public key .pem to a JWK

    • You can use an online tool to achieve this, such as jwkset

      • You do not need to enter key algorithm, use or operations, but key ID is preferred so that you are able to confirm which key was used for encryption when receiving the encrypted bank details

  4. Place your JWK inside a JWKS

    • Your JWKS should look something like this:

      { "keys": [ { "kty": "RSA", "n": "yR2pBvRpWMmRi385aJz8UoRuzEc9lSBbBMihNCuipqPguI9SazkahwPjoSWhms58zhrPUS5sOHL_f3FpkjhnHNYo-7Fz1DVw-MyxuFUgRetwb4LUf17H-tbHzD5DCB9HFqPFMxHRBrjtTUBHBYULelKuCEBMHkWVs0hK1g7huOo4PgGTWesKp8DSdLbg-ctkrKgtiUkrZpz7DjcHtPLIucEHRuXM-UIi9AINlR19AdMDRRm96mt3xT0moAyOdVcyGjfqbFZfaTBag7VX_qOnyoeS-J_D2AxzrEkFwoWt0qFcG1jHTqaFcs6NYsdE_zY051YLVY35GnQ0oc3M0lnK5w", "e": "AQAB", "kid": "9770a024c90fb646e48c952ec5d4f53586e62e8154048e6b96dd9f74f164a472" } ] }

Serving your JWKS

To make your public key available to GoCardless you will need to host the JWKS containing your public key and provide us with the URL.

You can serve your JWKS to GoCardless either as a static file or a response from a dynamic endpoint. The content needs to be served over HTTPS and the response returned needs to be in JSON format.

As a static file:

https://example.com/.wellknown/jwks.json

As a dynamic response:

https://example.com/my-api/jwks

Add your Public Key URL via the dashboard:

  1. Go to Developers

  2. Under Create

    1. Click Public Key

  3. Submit the URL which hosts your JWKS

Once you have successfully added your URL all admins in your organisation will receive an email notification of the change made to your public key URL.

Steps to Retrieve Bank Details

You can retrieve customer bank account details by providing any customer bank account ID to the /bank_account_details API.

If you want to retrieve customer bank details after they have completed a billing request, you can use the webhook notification GoCardless sends upon fulfilment of a billing request to retrieve the bank details by following these steps:

  1. Get the bank account identifier from the fulfilment webhook

    • You will find this in links.customer_bank_account of the fulfilled billing request object

  2. Send a request to the bank account details endpoint using the customer bank account identifier

    • GET /bank_account_details/<customer_bank_account_id>

REQUEST GET /bank_account_details/BA123 RESPONSES success - 200: return payload containing encrypted bank details: { bank_account_details: <encrypted payload> } error - 403: integrator doesn't have the required feature enabled: - Forbidden request error - 404: bank account doesn't exist: - Resource not found error - 422: failure to fetch or encrypt the bank details: - Organisation must have a valid public_key_url - Failed to get a successful response from the public_key_url - Organisation public_key_url must not redirect - public_key_url must provide JWKS - Key type is not supported by JWKS - GoCardless only supports 2048 bit RSA keys

Decrypting bank details

The response body from the /bank_account_details endpoint will contain an encrypted payload following Json Web Encryption (JWE) standards described in RFC7516.

The JWE Flattened format payload follows the structure shown in the example below and all values including the nested json header are base64 URL safe encoded.

Example response body from /bank_account_details/<customer_bank_account_id>:

{ "bank_account_details": { "protected": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00iLCJraWQiOiJiY2RkNDkzMmZhNjMxODY0MjI3YjhkZDZiMDdkMjQ5MDgyOGEzYWQ5OWM1OWMxNzc3ZDBjZTMzMDUyYjk5OGFiIn0", "encrypted_key": "pEQHNX_jCjKLIhZMa27C8cXgIlu7qUdrEcEuRwQw9EZyA_3vHG99gdd7YGQapVbSGEG43Hg5gi33Sgjsuekw6CumRFoBIKR7mANj5LUgfy6jFVFAC98RMiJ2H8yTBJlRiLonPRBILStNI8F7VU7cqqOOuCn7yeoeiJ7buSkqDW_ds9wrPC3uJgLZGyxPLU81R2WrE6b7hLVCpShLCBzH76wL3cPD5nYNT3eb5vqGl8SMb_mecZ-UnE5wfAzZhbU1CJ4qtlgFFiMt6HLFPvVAJBHlPdZaFcKotSjhOhk1rsySLrwKG9HmIBLykhED251jiIOyxTXIlNCzSMs23_R4XA", "iv": "6H4MLnhZGnwaWlQ2", "ciphertext": "Cntt_wZlkK4Ui7xI-BtAqf9V-FF41Cz8RxJd5KUGQJac", "tag": "WoaSncwUeLfU3IjgeFYtSQ" } }

The bank account details are encrypted using hybrid encryption, combining RSA and AES algorithms. The JWE payload includes a symmetric AES key, encrypted using your RSA public key, and a ciphertext, encrypted using the symmetric key.

Nested JWE payload properties explained:

Key

Value

protected

Protected header values, including:

alg: the asymmetric encryption type used to encrypt the symmetric key

enc: the symmetric encryption type used to encrypt the actual content (bank details)

kid: key ID of the public key used for asymmetric encryption

The strings included in the protected header follow the standard set in RFC7518 for alg values and enc values.

encrypted_key

Random symmetric key, encrypted with your public key.

The symmetric key generated for each request will be an AES 256 bit key, used to encrypt the data in Galois/Counter Mode (GCM).

iv

Initialisation vector - an arbitrary random number for seeding encryption.

ciphertext

Actual data (bank details) encrypted with the symmetric key

tag

Value to be recomputed on decrypt to validate the authenticity of the payload

Decryption Overview

The process to decrypt the received payload will consist of the following broad steps

  • Use the public_key_id to verify the identity of your key pair

  • Use your private key to decrypt the encrypted_key

  • Use the now decrypted encryption_key to decrypt the ciphertext

Detailed Decryption Steps

Many widely available public libraries exist to handle JSON Object Signing and Encryption (JOSE), and will be able to decrypt the response. You can also choose to handle decryption yourself.

The following is an example of how you could use a Javascript JOSE library to decrypt bank details:

import * as jose from 'jose'; /** * Decryption method * @param responseBody response body from /bank_account_details * @param privateKeyPEM PEM-encoded private key string * @param keyId key ID of the public JWK you expect GoCardless to have encrypted the response with */ async function decrypt(responseBody, privateKeyPEM, keyId) { const private_key = await jose.importPKCS8(privateKeyPEM, 'RSA-OAEP'); // Isolate JWE payload const JwePayload = responseBody['bank_account_details']; // Extract protected header contents, to verify the keyID matches. // NOTE: The Base64 encoded contents of the protected header is also used during // encryption as the Additional Authenticated Data (AAD), most libraries, including // jose, will include the protected header as AAD to be authenticated on // decryption automatically const protectedHeader = jose.decodeProtectedHeader(JwePayload); if (protectedHeader.kid !== keyId) throw new Error("Incorrect key id: " + keyId); // Decrypt data with the private key. const {plaintext} = await jose.flattenedDecrypt(JwePayload, private_key); // Decode the decrypted data. const decoder = new TextDecoder() // Pretty print the plaintext data. console.log(JSON.stringify(JSON.parse(decoder.decode(plaintext)), null, 2)) }

The following is an example of how you could handle decryption without a JOSE library, in Ruby using OpenSSL:

# response_body: GET /bank_account_details response body # private_key_pem: your private key PEM-encoded string # kid: the key ID of the public JWK you expect GoCardless to have encrypted the response with def decrypt(response_body:, private_key_pem:, kid:) # decode the response body jwe_payload = Hash[ response_body["bank_account_details"].map do |key, value| [key, Base64.urlsafe_decode64(value)] end] # parse the protected_header protected_header = JSON.parse(jwe_payload["protected"]) key_management_algorithm = protected_header["alg"] content_encryption_algorithm = protected_header["enc"] # check if protected_header kid matches expected kid raise StandardError if protected_header["kid"] != kid private_key = OpenSSL::PKey.read(private_key_pem) # decrypt the symmetric key content_encryption_key = private_key.private_decrypt(jwe_payload["encrypted_key"], padding_value(key_management_algorithm)) # instantiate a cipher and set the algorithm type to that contained in the jwe protected header's "enc" value decipher = OpenSSL::Cipher.new(cipher_value(content_encryption_algorithm)) decipher.decrypt # set cipher to decryption mode decipher.key = content_encryption_key decipher.iv = jwe_payload["iv"] decipher.auth_data = Utils.b64_encode(JSON.generate(protected_header)) decipher.auth_tag = jwe_payload["tag"] plaintext = decipher.update(jwe_payload["ciphertext"]) + decipher.final JSON.parse(plaintext) # => should return the decrypted object containing either local bank account details or an IBAN end

Interpreting the decrypted bank details

The decrypted bank details will be in IBAN format where applicable, or the local bank details otherwise.

IBAN format:

{ iban: "...", }

Local bank details format:

{ bank_code: "...", branch_code: "...", account_number: "...", }

Need help?