Virtual Accounts

Overview

The Virtual Accounts API offers an easy way to create and manage virtual accounts at your bank. A virtual account, also commonly referred to as a subaccount, is an account within a bank account. They can be used to manage transactions for individual customers or vendors, or to segment funds for different use cases or parts of your company. In some cases, virtual accounts can be a replacement for the need to have multiple DDA or FBO bank accounts.

To use the virtual accounts API, your bank must be able to create virtual account numbers (VAN) for your bank account. A VAN is an account number that can be used when transacting with your bank account. For example, let's say you have a bank account with an account number of 12345 and routing number 121141822. Your bank would allocate you a range of virtual account numbers that would map to your bank account. Let's say the range is 200xxxx. This means you can create a virtual account with a VAN of 2000001 and another with a VAN of 2000002.

These virtual accounts can be used to receive payments. When receiving payments, your counterparty would use a specified virtual account number and routing number. This will help you know exactly who the counterparty is in the transaction.

Creating a virtual account

The first step to using virtual accounts is to create one using our API. There are two strategies for creating the virtual account number for a virtual account, depending on the bank. Each bank's strategy is documented at the bottom of this guide.

The first strategy is to provide the virtual account number in your API request. This only works if your bank has provided you a range of virtual account numbers.

The second strategy is to not provide the virtual account number in your API request. If your bank has provided a range to you, Modern Treasury will automatically generate the account number on your behalf. If your bank generates virtual account numbers dynamically, then Modern Treasury will return the VAN in the response.

Both strategies are detailed below.

curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/virtual_accounts \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Funds on behalf of Alice Jones",
    "internal_account_id": "c743edb7-4059-496a-94b8-06fc081156fd",
    "account_details": [
      {
        "account_number": "2000001",
        "account_number_type": "other"
      }
    ]
  }'
curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/virtual_accounts \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Funds on behalf of Alice Jones",
    "internal_account_id": "c743edb7-4059-496a-94b8-06fc081156fd"
  }'

You will get a response which includes the full details of the virtual account, including the virtual account number and routing number.

{
  "id": "2e0296c1-1daf-4b3e-954d-fb9ec7be56f6",
  "object": "virtual_account",
  "name": "Funds on behalf of Alice Jones",
  "description": null,
  "active": true,
  "counterparty_id": null,
  "internal_account_id": "c743edb7-4059-496a-94b8-06fc081156fd",
  "debit_ledger_account_id": null,
  "credit_ledger_account_id": null,
  "account_details": [
    {
      "id": "5668c0cf-972d-49c6-970f-b32591f3e8a6",
      "object": "account_detail",
      "account_number": "2000001",
      "account_number_type": "other"
    }
  ],
	"routing_details": [
    {
      "id":"5ceb251f-0235-48a2-81cb-0c668f5ee81b",
      "object": "routing_detail",
      "payment_type": null,
      "routing_number": "121141822",
      "routing_number_type": "aba",
      "bank_name": "BANK OF AMERICA CALIFORNIA, NA",
      "bank_address": {
        "id": "2f1e12dd-de80-44aa-92cd-f0e4101b8e54",
        "object": "address",
        "line1": "PO BOX 27025",
        "line2": null,
        "locality": "RICHMOND",
        "region": "VA",
        "postal_code": "23261-7025",
        "country": "US",
        "live_mode": true,
        "created_at": "2019-11-09T00:11:07Z",
        "updated_at": "2019-11-09T00:11:07Z"
      },
      "live_mode": true,      
      "created_at": "2019-11-09T00:11:07Z",
      "updated_at": "2019-11-09T00:11:07Z"
    }
  ],
  "metadata": {},
  "live_mode": true,
  "created_at": "2020-11-09T00:11:07Z",
  "updated_at": "2020-11-09T00:11:07Z"
}

Creating a virtual account for a counterparty

Our API supports linking a virtual account to a counterparty. This may be helpful if you are holding funds on behalf of someone, or if you want to link a set of transactions to a counterparty. Your bank may require you to associate the virtual account to a counterparty as well. The API call for creating a virtual account linked to a counterparty would look like this.

curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/virtual_accounts \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Funds on behalf of Alice Jones",
    "internal_account_id": "c743edb7-4059-496a-94b8-06fc081156fd",
    "counterparty_id": "b4313c66-3892-416d-995f-f5b6044b5c7a",
    "account_details": [
      {
        "account_number": "2000001",
        "account_number_type": "other"
      }
    ]
  }'

Receiving payments in a virtual account

Virtual accounts can be helpful when you want a counterparty to send money to you or pull funds from your account. Before a payment like this, you would provide the counterparty with your virtual account number, 2000001, and your routing number, 121141822.

For this example, we will assume the counterparty has sent $10 to your account. At some point between when the counterparty sends you the funds and when they arrive, you will receive an Incoming Payment Detail webhook. The event will be incoming_payment_detail.created. The incoming payment detail (IPD) will include details about the transaction that is about to hit your bank account. IPDs may arrive days before the transaction actually hits your account, like in the case of payroll. The object's data will include information like the expected settlement date.

When the transaction hits your account, you will initially receive an incoming_payment_detail.tentatively_reconciled webhook. This means that you have a pending transaction in your bank account, which comes via intraday data from the bank.

Finally, you will receive an incoming_payment_detail.completed webhook when the funds have posted in your bank account. This is usually the following banking day after when the pending transaction arrived and the tentative reconciliation occurred.

The incoming payment detail webhook body will include the virtual_account_id. A sample incoming payment detail webhook would look like this.

{
  "event": "created",
  "data": {
    "id": "4844bda1-eb57-4129-afb4-fc663a395ca0",
    "object": "incoming_payment_detail",
    "internal_account_id": "487b9a5c-6737-43ea-b11c-593f39226b92",
    "virtual_account_id": "fd3c1f59-6d5b-466c-9126-7f8c82ad6e1c",
    "virtual_account": {
      "id": "fd3c1f59-6d5b-466c-9126-7f8c82ad6e1c",
      "object": "virtual_account",
      "name": "Funds on behalf of Alice Jones",
      "description": null,
      "counterparty_id": null,
      "internal_account_id": "c743edb7-4059-496a-94b8-06fc081156fd",
      "debit_ledger_account_id": null,
      "credit_ledger_account_id": null,
      "account_details": [
        {
          "id": "5668c0cf-972d-49c6-970f-b32591f3e8a6",
          "object": "account_detail",
          "account_number": "2000001",
          "account_number_type": "other"
        }
      ],
      "routing_details": [
        {
          "id":"5ceb251f-0235-48a2-81cb-0c668f5ee81b",
          "object": "routing_detail",
          "payment_type": null,
          "routing_number": "121141822",
          "routing_number_type": "aba",
          "bank_name": "BANK OF AMERICA CALIFORNIA, NA",
          "bank_address": {
            "id": "2f1e12dd-de80-44aa-92cd-f0e4101b8e54",
            "object": "address",
            "line1": "PO BOX 27025",
            "line2": null,
            "locality": "RICHMOND",
            "region": "VA",
            "postal_code": "23261-7025",
            "country": "US",
            "live_mode": true,
            "created_at": "2019-11-09T00:11:07Z",
            "updated_at": "2019-11-09T00:11:07Z"
          },
          "live_mode": true,      
          "created_at": "2019-11-09T00:11:07Z",
          "updated_at": "2019-11-09T00:11:07Z"
        }
      ],
      "metadata": {},
      "live_mode": true,
      "created_at": "2020-11-09T00:11:07Z",
      "updated_at": "2020-11-09T00:11:07Z"    
    },
    "transaction_line_item_id": null,
    "transaction_id": null,
    "type": "ach",
    "data": {
      "batch_header_record": {
        "batch_number": "999",
        "company_name": "moderntreasury.com",
        "settlement_date": 17,
        "service_class_code": "200",
        "effective_entry_date": "2019-01-30",
        "company_identification": "9999999999",
        "originator_status_code": "1",
        "company_descriptive_date": "",
        "company_entry_description": "moderntreasury.co",
        "standard_entry_class_code": "CCD",
        "company_discretionary_data": "",
        "originating_dfi_identification": "99999999"
      },
      "detail_record": {
        "amount": 10000,
        "trace_number": "999999999999999",
        "transaction_code": "27",
        "dfi_account_number": "99999",
        "discretionary_data": "",
        "identification_number": "moderntreasury.com",
        "receiving_company_name": "EXAMPLE INC",
        "addenda_record_indicator": true
      },
      "payment_related_information": "Lorem Ipsum",
    },
    "amount": 10000,
    "currency": "USD",
    "direction": "debit",
    "status": "pending",
    "metadata": {},
    "as_of_date": "2020-10-17",
    "live_mode": true,
    "created_at": "2020-10-15T04:23:11Z",
    "updated_at": "2020-10-15T04:23:11Z"
  }
}

There may be cases where you want to initiate a return against a transaction you've received, such as if the transaction was unexpected or for too much money. In that case, you can originate an ACH return or a wire return using the incoming payment detail's ID.

Supported Banks

BankSupported?Virtual Account Number schemeSupported inbound payment types
Goldman SachsYesDynamic bank generationACH
Fedwire
SWIFT wire
IncreaseYesDynamic bank generationACH
Fedwire
JP Morgan ChaseYesAccount number rangeACH
Fedwire
SWIFT wire
Wells Fargo BankYesAccount number rangeACH
Fedwire
SWIFT wire
BankProvYesAccount number rangeFedwire
Silicon Valley BankYesAccount number rangeACH

📘

Virtual Account Number schemes

For banks with the Dynamic bank generation scheme, your bank will automatically generate virtual account numbers on your behalf. You cannot supply the account number in your API request.

For banks with the Account number range scheme, you may supply the account number in your API request. If you do not, Modern Treasury will allocate it on your behalf.