GoCardlessDeveloper Docs
Create a sandbox account

Bank Details Access#

View as Markdown

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 public/private key pair and upload your public key in the GoCardless dashboard. 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 public/private key pair and add your public key in the GoCardless dashboard.

Generating your key pair#

  1. Generate an RSA-2048 public/private key pair
openssl genrsa -out privatekey.pem 2048
openssl rsa -in privatekey.pem -pubout -out publickey.pem
  1. Securely store your private key
  2. Copy your public key PEM

Upload your public key#

To make your public key available to GoCardless you will need to upload your public key in the dashboard:

  1. Go to Developers.
  2. Under Create 1. Click Public Key
  3. Click Add public key 1. Enter a Name - This will help you to identify your public key in the dashboard
  4. Enter your Public key - Paste your generated public key PEM into the input box
  5. Click Add
  6. Copy **and save **the generated Key ID - You will need to use this ID to tell GoCardless which public key to use to encrypt data when making requests to the /bank_account_details endpoint
  7. Click Continue

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
  • including the public key ID you saved earlier as a request header GC-Key-Id: PK123

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
 
HEADERS
- Gc-Key-Id (required): the key ID of your uploaded public key to use for encryption.
 
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:
    - Ensure your Gc-Key-Id header contains a valid public key ID
 

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:

KeyValue
protectedProtected 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_keyRandom 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).
ivInitialisation vector - an arbitrary random number for seeding encryption.
ciphertextActual data (bank details) encrypted with the symmetric key
tagValue 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 in countries where IBAN is supported, or the local bank details otherwise.

IBAN format:

{
  "iban": "..."
}

Local bank details format:

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

What's next?#