Skip to content

Implementing cash balance transfers

Prerequisites

Before implementing cash balance transfers, ensure you have:

  • Authentication scopes -
    • cash_balance_transfers:admin to create and cancel transfers.
    • cash_balance_transfers:read to retrieve and list them.
    • positions:read: to check the cash balance.
  • Webhook handler ready - Set up a webhook endpoint to listen for transfer lifecycle events and subscribe to CASH_BALANCE_TRANSFER.* events.

Transferring cash between account groups

Step 1: Confirm the available cash balance

Retrieve the cash balance of the source account group and check that available_for_withdrawal covers the amount you intend to transfer.

Example request for the source cash balance

GET /account_groups/{account_group_id}/payments/cash_balances

{
 "meta": {
  "count": 1,
  "total_count": 1,
  "offset": 0,
  "limit": 100
 },
 "data": [
  {
   "account_group_id": "2596db3b-0d03-4651-9eda-970910479dfb",
   "currency": "EUR",
   "balance": "250",
   "locked_for_trading": "50",
   "pending_settlement": "0",
   "available_for_withdrawal": "200",
   "available_for_trading": "200"
  }
 ]
}

In this example, EUR 200.00 can be transferred. The remaining EUR 50.00 is reserved for a pending order as shown by the locked_for_trading value.

Step 2: Create the transfer

Create the transfer by specifying the source and target account groups, the amount, and the currency. The request requires an idempotency-key header so that a retried request does not create a second transfer.

Example request to create the transfer

POST /payments/cash_balance_transfers

{
 "source_account_group_id": "2596db3b-0d03-4651-9eda-970910479dfb",
 "target_account_group_id": "e56c14b3-8370-4c04-bc67-7092f7ec113c",
 "amount": "200.00",
 "currency": "EUR"
}

The request fields are as follows.

FieldRequiredDescription
source_account_group_idRequiredThe account group the cash is transferred from. Must differ from the target_account_group_id.
target_account_group_idRequiredThe account group the cash is transferred to. Must belong to the same user and client as the source, and be of the same account group type.

Only PERSONAL, CHILD, or BUSINESS account group types are supported.
amountRequiredThe amount to transfer, expressed as a whole number or with exactly two decimal places. Must not exceed the source account group’s available_for_withdrawal amount.
currencyRequiredThree-letter ISO 4217 currency code. This field supports EUR (euro) and GBP (pound sterling).

The response returns the transfer object, which adds the following fields.
FieldRequiredDescription
idN/AUnique identifier of the transfer. Use it as your reference for the transfer.
statusN/AReflects the transfer’s current status. This field supports ISSUED, CONFIRMED, and CANCELLED.
cancellation_reasonN/AThe reason the transfer was cancelled. Present only when the status is CANCELLED. Otherwise, the value is null.
created_at, updated_atN/AWhen the transfer was created and last updated.

All validation happens before any cash moves, so a rejected request never creates a transfer. A request is rejected with a 400 error code if any of the following occurs:

  • The source and target account group are the same.
  • The account groups do not belong to the same holder.
  • The source and target account group types do not match.
  • The account group types are different than either PERSONAL, CHILD, or BUSINESS.
  • The source account group does not hold enough settled cash.

Webhooks notify you of events in business processes, not of the success or failure of initialising them. Check the HTTP status of the response to your request and handle failure cases accordingly.

Step 3: Track the transfer status

Rather than polling, listen for the following events:

  • CASH_BALANCE_TRANSFER.ISSUED
  • CASH_BALANCE_TRANSFER.CONFIRMED
  • CASH_BALANCE_TRANSFER.CANCELLED

Each event carries the full transfer details in the object field.

Example event for a confirmed transfer

{
	 "id": "a59c81ee-7770-4f6b-9ebe-8c268b78beb4",
	 "created_at": "2026-06-25T14:15:23Z",
	 "type": "CASH_BALANCE_TRANSFER.CONFIRMED",
	 "object": {
		 "id": "b3f6a0c2-1d7e-4a3b-9f21-0c2d4e6f8a10",
		 "created_at": "2026-06-25T14:15:22Z",
		 "updated_at": "2026-06-25T14:15:23Z",
		 "source_account_group_id": "2596db3b-0d03-4651-9eda-970910479dfb",
		 "target_account_group_id": "e56c14b3-8370-4c04-bc67-7092f7ec113c",
		 "amount": "200.00",
		 "currency": "EUR",
		 "status": "CONFIRMED",
		 "cancellation_reason": null
	 },
	 "webhook_id": "9df39835-be87-4243-9018-f2500b39cee6"
 }

You will also receive a CASH_BALANCE.UPDATED event for each of the two account groups once the transfer is confirmed.

Retrieve and list transfers

You can retrieve details of a specific cash balance transfer, and list all transfers for an end user or a business, at any time.

To retrieve a specific transfer:

GET /payments/cash_balance_transfers/{cash_balance_transfer_id}.

To retrieve a list of transfers for an end user:

GET /users/{user_id}/payments/cash_balance_transfers.

To retrieve a list of transfers for a business:

GET /businesses/{business_id}/payments/cash_balance_transfers.

List results can be sorted by created_at or id.

Step 4: Cancel a transfer

A transfer can only be cancelled while it has the status ISSUED.

Example request to cancel a cash balance transfer

DELETE /payments/cash_balance_transfers/{cash_balance_transfer_id}

A successful cancellation returns a 204 No Content status code. No cash is moved, the transfer’s status becomes CANCELLED, and cancellation_reason explains why. Attempting to cancel a transfer that is already CONFIRMED or CANCELLED is rejected.