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:
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.
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
Generate an RSA-2048 private key
openssl genrsa -out privatekey.pem 2048 openssl rsa -in privatekey.pem -pubout -out publickey.pem
Securely store your private key
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
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:
Go to Developers.
Under Create
Click Public Key
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:
Get the bank account identifier from the fulfilment webhook
You will find this in
links.customer_bank_account
of the fulfilled billing request object
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:
The strings included in the protected header follow the standard set in RFC7518 for |
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 pairUse your private key to decrypt the
encrypted_key
Use the now decrypted
encryption_key
to decrypt theciphertext
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: "...",
}