Skip to main content

Collect Bank Accounts

When building an e-commerce application, subscription service, payroll solution, or just need to enable one-time purchases via direct debt (pay by bank) or fund transfers, one of the critical requirements is to collect and store bank accounts information securely. However, bank routing numbers paired with account numbers, are considered sensitive data and need to be captured and stored following strict security requirements.

In this guide, we will set up Basis Theory SDKs to capture Bank Accounts in a frontend, Web or Mobile application, and securely store the data as tokens in Basis Theory Platform, NACHA preferred partner for data tokenization and encryption. Given this guide is followed step by step, you can rest assured that you are complying to world's class security and privacy standards.

Collect Bank Accounts Flowchart

Getting Started

To get started, you will need a Basis Theory account and a Tenant.

Creating a Public Application

Next you will need a Public Application using our NACHA-compliant template Collect Bank Data. Click here to create one.

This will create an application with the following Access Controls:

  • Permissions: token:create, token:update
  • Containers: /bank/
  • Transform: mask
Save the API Key from the created Public Application as it will be used later in this guide.

Configuring Basis Theory Elements

Basis Theory Elements are available for the following technologies. Click below for detailed instructions on how to install and configure them.

Javascript
React
iOS
Android
React Native

Adding Text Elements

Once installed and configured, add the Text Elements to your application. This will enable users to type in their bank account information in your form, while ensuring your systems never touch the data.

<div id="routingNumber" style="width: 100%;"></div>
<div id="accountNumber" style="width: 100%;"></div>
import { BasisTheory } from '@basis-theory/basis-theory-js';

let bt;
let routingNumberElement;
let accountNumberElement;

async function init() {
bt = await new BasisTheory().init('<API_KEY>', { elements: true });

// Creates Elements instances
routingNumberElement = bt.createElement('text', {
targetId: 'myRoutingNumber', // (custom) used for tracking validation errors
placeholder: 'Routing Number',
mask: [
/\d/u,
/\d/u,
/\d/u,
/\d/u,
' ',
/\d/u,
/\d/u,
/\d/u,
/\d/u,
' ',
/\d/u,
],
transform: /\s/u, // strip out spaces from mask above before tokenization
});
accountNumberElement = bt.createElement('text', {
targetId: 'myAccountNumber',
placeholder: 'Account Number',
});

// Mounts Elements in the DOM in parallel
await Promise.all([
routingNumberElement.mount('#routingNumber'),
accountNumberElement.mount('#accountNumber'),
]);
}

init();

Storing Bank Accounts

Now that you are securely capturing the bank account data in your user-facing application(s), it is time to store it in your Basis Theory Tenant.

To do this, we will invoke the Create Token endpoint from the SDK, passing the Text Elements as data points in the payload. This will securely create a bank token by transferring the bank information from the frontend Elements to Basis Theory, where the data will be strongly encrypted and stored in a compliant environment.

Add a submit function along with a button to trigger it:

<div id="cardNumber"></div>
<div style="display: flex;">
<div id="cardExpirationDate" style="width: 100%;"></div>
<div id="cardVerificationCode" style="width: 100%;"></div>
</div>
<button id="submit">Submit</button>
import { BasisTheory } from '@basis-theory/basis-theory-js';

let bt;
let routingNumberElement;
let accountNumberElement;

async function init () {
...
document.getElementById("submit").addEventListener("click", submit);
}

async function submit () {
try {
const token = await bt.tokens.create({
type: 'bank',
data: {
routing_number: routingNumberElement,
account_number: accountNumberElement,
}
});
// store token.id in your database
} catch (error) {
console.error(error);
}
}

init();
Be sure to replace <API_KEY> with the Public API Key you created in the Creating a Public Application step.

The created bank token object contains the non-sensitive information about the tokenized card, which follows the Bank Token specification:

{
"id": "f910b9aa-a4a6-4f24-9ec4-2de1a5731d0b",
"type": "bank",
"tenantId": "4aee08b9-5557-474b-a120-252e01fc7b0f",
"data": {
"routing_number": "021000021",
"account_number": "XXXXX3123"
},
"createdBy": "f5c44560-8433-4dcc-b67f-53594c397a5e",
"createdAt": "2023-10-26T14:27:10.6126956+00:00",
"mask": {
"routingNumber": "{{ data.routing_number }}",
"accountNumber": "{{ data.account_number | reveal_last: 4 }}"
},
"privacy": {
"classification": "bank",
"impactLevel": "high",
"restrictionPolicy": "mask"
},
"searchIndexes": [],
"containers": [
"/bank/high/"
]
}

You can safely store the token's primary key id in your database to link it with the appropriate checkout, user profile, subscription, or any other record that requires association with the bank details.

Notice how the bank account_number has been masked before it is returned to your application. This default behavior prevents your application having to deal with the sensitive part of the bank details. Click here to learn more about masking and see how to customize this behavior.

Customizing Tokens

The steps so far cover most cases when you need to collect bank accounts in your frontend and store them in a secure location. However, in some scenarios you may need to customize your bank tokens for specific business needs or technical requirements.

Click here to learn more about Basis Theory Token capabilities.

Deduplication

Companies often find it necessary to uniquely identify bank accounts flowing through their systems for various reasons, which may include: preventing fraudulent transactions, detecting abuse, building consumer profiles or streamlining payment processing for a better user experience.

By leveraging token fingerprinting, developers can recognize the tokenized data in a customizable fashion, without having to interact with the plaintext data. For bank details, it is common to index in both Routing Number and Account Number, but in some cases additional consumer details may also be considered.

When making the tokenization request to store the bank data, pass a fingerprint expression to instruct Basis Theory to calculate the fingerprint for the sensitive data field:

async function submit () {
try {
const token = await bt.tokens.create({
type: 'bank',
data: {
routing_number: routingNumberElement,
account_number: accountNumberElement,
},
fingerprintExpression: '{{ data.account_number }}|{{ data.routing_number }}',
});
} catch (error) {
console.error(error);
}
}

The new tokens should now have a fingerprint:

{
"id": "f910b9aa-a4a6-4f24-9ec4-2de1a5731d0b",
"type": "bank",
"tenantId": "4aee08b9-5557-474b-a120-252e01fc7b0f",
"data": {
"routing_number": "021000021",
"account_number": "XXXXX3123"
},
"createdBy": "f5c44560-8433-4dcc-b67f-53594c397a5e",
"createdAt": "2023-10-26T14:27:10.6126956+00:00",
"fingerprint": "CC2XvyoohnqecEq4r3FtXv6MdCx4TbaW1UUTdCCN5MNL",
"fingerprint_expression": "{{ data.number }}",
"mask": {
"routingNumber": "{{ data.routing_number }}",
"accountNumber": "{{ data.account_number | reveal_last: 4 }}"
},
"privacy": {
"classification": "bank",
"impactLevel": "high",
"restrictionPolicy": "mask"
},
"searchIndexes": [],
"containers": [
"/bank/high/"
]
}

If you want to prevent creation of a duplicate token based on the distinguishable fingerprint, add the flag below:

async function submit () {
try {
const token = await bt.tokens.create({
type: 'bank',
data: {
routing_number: routingNumberElement,
account_number: accountNumberElement,
},
fingerprintExpression: '{{ data.account_number }}|{{ data.routing_number }}',
deduplicateToken: true,
});
} catch (error) {
console.error(error);
}
}

By doing the above, you are instructing Basis Theory to return the existing token if it is found to have the same fingerprint. Click here to learn more about token deduplication.

Conclusion

The best practices prescribed in this guide ensure that your user-facing applications can collect sensitive bank account information securely, while protected. The token.id obtained at the end of the Storing Bank Accounts section is a synthetic replacement for the sensitive data and can be safely stored in your database, or transmitted through your systems, meeting security and privacy requirements while reducing the risk of exposure in case of data breaches.

For next steps, take a look at the following guides to proceed taking the most value of your secured bank tokens: