Discover our latest AI-powered innovations around faster payments, smarter workflows, and real-time visibility.Learn more →

Journal

How We Built Virtual Accounts

On Tuesday, we announced Virtual Accounts, our new product that helps automate payment attribution. Here's the story behind how we built it.

Matt McFarlandEngineering

Introduction

From its outset, Modern Treasury solved the payment origination problem. We made it easy for customers to send money to recipients across a matrix of payment rails, currency and countries.

On Tuesday, we announced our Virtual Accounts product, which solves the other side of the payment operations problem: inbound payment attribution. As customers grow and receive more payments originated by external parties, the reconciliation of those payments to their senders becomes increasingly complex and burdensome.

Our Virtual Accounts Product

Modern Treasury’s Virtual Accounts can automatically reconcile and tag inbound payments with their sender by allocating a unique account number to which each external originating party can send payments. Customers can designate one—or more—virtual accounts per counterparty to handle received funds, which makes tracking the payments from each of those individuals unambiguous. Payments sent to a virtual account appear in the physical Internal Account that the virtual account range is mapped to (the bank owns this mapping process).

Types of Virtual Account Allocations

The banks that provide virtual accounts expose a wide range of virtual account implementations, which can be represented by:

  • A common prefix (“123456XXXX”)
  • A common suffix (“XXXX123456”)
  • A range (“12345[1111-9999]”)
  • A bank-determined list of reserved numbers (“123450”, “123455”, “123459”, etc…)

Our Virtual Accounts API unifies how a customer can create virtual accounts in any of these schemes by handling the account allocation logic in each scenario.

How It Works

When we were designing the Virtual Accounts API, we knew that the API would need to handle the variety of virtual account allocation schemes offered within the banking ecosystem. Customers needed the virtual account creation API to pick a free virtual account number within the range defined by their bank. Banks offering virtual accounts have various allocation types, so we needed our implementation to abstract away the complexity of those types, and identify a usable account virtual number if the customer does not specify one.

The API requires the minimum information we need to attribute a virtual account to which a counterparty can send payments. The following example shows how to create a virtual account and the response.

In this example, let’s say that the Bank of MT provided a virtual account range 1234XXXXXX [1] attached to the Internal Account for Sesame Inc.’s ID: “56324045-5eeb-49b0-8714-c3a1f48884a1.”

That virtual account range would include all account numbers ranging from 1234000000 - 1234999999, giving us 100,000 possible virtual account numbers.

Payments can be sent to the Sesame Inc.’s virtual account by specifying the routing number 121141822 and account number 1234560000. In this API call, Modern Treasury found a free account number within the virtual account allocation and claimed it for Sesame Inc.’s virtual account.

How We Built It

Defining the logic to identify and claim account numbers within an allocation ended up being an interesting problem to solve. We needed this process to be performant and correct. The rest of this blog post discusses our journey to finding a solution that fit both criteria.

The Naive / Brute-Force Approach

In the first Virtual Account implementation at Modern Treasury, we wrote the following algorithm to find an unassigned account number in a prefix range.

This simple algorithm repeatedly pulls random account numbers out of the range, determines if a collision exists for that customer’s allocation, and then re-rolls if there is one.

After a moment's thought, it became clear this algorithm had a major flaw. If every possible account had been provisioned in the range, this algorithm would loop endlessly.

Brute Force, Version 2

We tried our hand again with an implementation that capped attempts so the algorithm wouldn’t be recursive.

We can at least complain loudly and abort if an allocation finding exceeds a certain number of attempts, but even then, if a successful and growing customer starts to allocate a significant portion of their allowed range, we might run into collision exceptions as the norm.

Though this implementation will not infinitely spin, it has another problem. Since this allocation algorithm is run synchronously during the API call (without a given account number), these API calls could grow increasingly expensive as the number of expected iterations to find a free account number rises. This didn’t sit right with us, and we rethought our approach again.

Queues and Asynchronous Processing

After reviewing our initial implementation and realizing that we needed to find a sturdier and more performant virtual account number search strategy. We started by reviewing our requirements:

  1. The algorithm needed to be able to find a free account number in constant O(1) time.
  2. The solution also needed to handle the real-world constraint that an allocation might become exhausted.

We realized we could satisfy both requirements by separating the calculation of free account numbers from allocating those numbers to virtual accounts during virtual account creation. We decided to add a queue data structure for each customer's virtual account allocation range. Each queue contained a list of unallocated account numbers found by a background processing job. It turned the synchronous provisioning logic into a much simpler process.

The background processor would periodically scan all of the virtual account ranges for queues that had been depleted below our desired threshold.

Extending the Solution

By separating how Modern Treasury identifies available virtual accounts from the allocation of those virtual account numbers, we were able to abstract over the various types of virtual account products offered by banks. The Virtual Account creation API retains a simple and performant interface, while our background processors handle the complexity of enforcing uniqueness across virtual accounts within a customer's allocated range.

If you have any questions or feedback, feel free to reach out to support@moderntreasury.com. Likewise, if you enjoy designing well-abstracted APIs, Modern Treasury is hiring software engineers.

  1. This type of range allocation is something we call “prefix” range allocation. In the virtual account product landscape, banks are also suffix allocations, range allocations, and reserved allocations, all of which can be handled with a similar provisioning strategy.

Try Modern Treasury

See how smooth payment operations can be.

Talk to sales