Our landmark annual report is here.Read the State of Payment Operations 2022


How to Handle Concurrent Transactions

Image of Carly Steckel
Carly SteckelProduct


Every time you check a user balance in your app, you get a snapshot of that balance at a moment in time. As money moves, you need to be confident that that snapshot is accurate even as balances are constantly shifting in real time.

To prevent a double-spend situation, your ledger needs to be able to record transactions that are conditional on the state of an account balance or account version. The Ledgers API already supports locking on the account version. Today, we’re excited to ship support for account balance locking to make it even easier to handle concurrency in your ledger.

What is Concurrency?

Concurrency causes issues when there are multiple simultaneous demands to move money out of an account. Each process takes a snapshot of the account balance to determine if there are sufficient funds to initiate a payment. While there might be sufficient funds to power each payment separately, there might not be sufficient funds for both. As a result, when both payments are written to the ledger, the account would be overdrawn. This scenario is referred to as a “race condition”: two or more API calls attempting to alter the same data at the same time, but in order to be carried out correctly, they must be done in sequence.

For example, suppose your application allows a user to draw down their in-app balance either by swiping a virtual card or through an in-app purchase. If the user tries to do both at once, the two processes may race to record the spend of money in your ledger, and your application may allow the user to double-spend their balance.

Locking on account balances

In most cases, your app only needs to be sure that there will be sufficient funds available for a desired transaction. The transaction should be written on the condition that the account balance be within a given range.

In the Ledgers API, ledger accounts represent important balances, and ledger entries write changes to these balances. Multiple ledger entries can be written through the Create Ledger Transaction endpoint.

You can specify a required balance (range or exact amount) when writing an entry to the ledger account. The transaction will only be created if the ledger account balance would be within the specified range after the transaction is created. Note that the balance specified is the required balance after the transaction is created, not before. If the requirement cannot be met, the transaction will fail with error code 422.

Thanks to account balance locking, with a single API call you can ensure that a transaction will post if and only if the ledger account balance will remain within a specified range or amount.

For example, suppose you are recording a payment moving $1 out of a credit-normal user account. You can specify that the balance must remain greater than $0 upon completion of the transaction:

1curl --request POST \
2     --url \
3     --header 'Accept: application/json' \
4     --header 'Content-Type: application/json' \
5     --data '
7		"effective_date": "2022-01-01",
10     "ledger_entries": [
1112          {
13             "amount": 100,
14             "direction": "debit",
15						 "balances": {
16						   "pending_balances": {
17						     "amount": {
18						       "gte": 0
19					      }
20					    }
21             "ledger_account_id": "xxx"
22          }
23     ]

Using the lock_version field

In some cases, your app may need to be sure that a ledger account has not changed at all since your last snapshot. A transaction can be written on the condition that the account is at a given lock_version. Whenever an entry is written to a ledger account, its version number, or lock_version, is automatically incremented.

You can specify a lock_version when writing an entry to the ledger account. If the lock_version specified is not the same as the current lock_version of the ledger account balance, the transaction will fail to be created.

For example, suppose you are recording a payment paying out $10.32 from a credit-normal user account. You can specify that the transaction be written if and only if the ledger account has not been updated since you last checked its lock_version:

1curl --request POST \
2     --url \
3     --header 'Accept: application/json' \
4     --header 'Content-Type: application/json' \
5     --data '
7	"description": "Payout",
8	"effective_date": "2022-01-01",
9	"status": "pending",
10	"external_id": "97dbb8b1-e6f2-485e-a0ec-6267e3c60718",
11	"ledger_entries": [
12		{
13			"amount": 1032,
14			"direction": "debit",
15			"lock_version": 5
16		},
1718	]

If the lock_version of your ledger account balance is not 5, meaning there has been another entry written to the account since lock_version 5, the transaction will fail.

Get Started

To start tracking your business’s money movement with Modern Treasury, sign up for a free Ledgers account today. And as always, don’t hesitate to get in touch—we’d love to talk through your specific use case.

Try Modern Treasury

See how smooth payment operations can be.

Talk to Us