Virtual Accounts

Create single or multi use virtual accounts via the Atlas API.

Atlas supports two types of virtual accounts, single use and multi-use. The single use virtual accounts expire once a payment is completed on it or the assigned expiry reaches (whichever comes first). A multi-use virtual account can be re-used for multiple transactions and does not expire. There is also another category of virtual accounts under multi-use virtual accounts that are tied to customers, known as DVAs (dedicated virtual accounts). DVAs are assigned to customers automatically when you create them on Atlas.

DVAs and has_wallet

A customer only gets a DVA automatically if you set has_wallet to true when you create them. See Creating a customer in the Customers guide for details, or attach one to an existing customer who doesn't have one yet.

Creating a virtual account

To create a virtual account for your business, make a request to the Create Virtual Account endpoint. Accounts settle into your business collection wallet for the account's currency.

The request body depends on the account_type you're creating:

  • account_name (required): the name shown to payers on transfer, between 3 and 250 characters.
  • provider_name (required): globus, wema, or duplo. Test API keys may only use duplo; live API keys may only use globus or wema. Only wema accounts can be updated after creation, globus and duplo accounts are fixed once created.
  • source_reference (required): your own unique reference for the source of this virtual account, between 3 and 250 characters. Use it to look the account up later with Get Virtual Account.
  • amount (optional): a fixed amount expected on the account. Omit it to accept any amount.
  • currency (optional): defaults to NGN when omitted. Must be a currency currently enabled for virtual accounts on your business.
  • expires_at (required for single_use, omit for multi_use): formatted as ISO 8601 (YYYY-MM-DD HH:MM:SS), for example 2026-07-20 18:30:00. Must be in the future and within 24 hours.
{
  "account_type": "single_use",
  "account_name": "Acme Store",
  "provider_name": "wema",
  "source_reference": "src_1234567890",
  "amount": 5000,
  "expires_at": "2026-07-20 18:30:00"
}

The examples below show how to send this request for a single_use account:

curl -X POST "https://atlas.tryduplo.com/api/v1/virtual-account" \
  -H "Authorization: Bearer your_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "account_type": "single_use",
    "account_name": "Acme Store",
    "provider_name": "wema",
    "source_reference": "src_1234567890",
    "amount": 5000,
    "expires_at": "2026-07-20 18:30:00"
  }'

On a successful request, you'll get a response similar to the one below:

JSON
{
  "requestId": "abcd1234-5678-90ef-ghij-1234567890kl",
  "requestTimestamp": "2026-07-20 09:15:00",
  "message": "Virtual account created successfully.",
  "statusCode": 201,
  "data": {
    "id": "019a1639-ce25-70b7-ab20-16fcb2bac023",
    "businessId": "019a1639-ce25-70b7-ab20-16fcb2bac024",
    "businessState": "TEST",
    "amount": {
      "value": 5000,
      "currency": "NGN",
      "formatted": "NGN5000"
    },
    "reference": "VA_A1B2C3D4E5F6",
    "walletId": "019a1639-ce25-70b7-ab20-16fcb2bac025",
    "sourceReference": "src_1234567890",
    "accountNumber": "1234567890",
    "accountName": "Acme Store",
    "providerName": "wema",
    "accountType": "single_use",
    "status": "active",
    "currency": "NGN",
    "expiresAt": "2026-07-20 18:30:00",
    "createdAt": "2026-07-20 09:15:00"
  }
}

Listing virtual accounts

To retrieve virtual accounts belonging to your business, make a GET request to the List Virtual Accounts endpoint.

The supported query parameters are:

ParameterDescription
referenceFilter by the Atlas-generated account reference.
accountNumberFilter by the account number.
accountTypeFilter by single_use or multi_use.
statusFilter by derived status, active, inactive, or expired. An account past its expiry is always expired, even if this filter is set to active.
currencyFilter by currency, for example NGN.
searchPartial match on reference, account number, or account name.
startDate / endDateFilter by creation date range.
sortSort direction on createdAt, ASC or DESC. Defaults to DESC.
limitResults per page, defaults to 1000.
pagePage number, defaults to 1.
curl -X GET "https://atlas.tryduplo.com/api/v1/virtual-account?accountType=single_use&status=active&limit=10&page=1" \
  -H "Authorization: Bearer your_API_KEY" \
  -H "Accept: application/json"

On a successful request, you'll get a response similar to the one below:

JSON
{
  "requestId": "abcd1234-5678-90ef-ghij-1234567890kl",
  "requestTimestamp": "2026-07-20 09:15:00",
  "message": "Virtual accounts retrieved successfully.",
  "statusCode": 200,
  "data": [
    {
      "id": "019a1639-ce25-70b7-ab20-16fcb2bac023",
      "businessId": "019a1639-ce25-70b7-ab20-16fcb2bac024",
      "businessState": "TEST",
      "amount": {
        "value": 5000,
        "currency": "NGN",
        "formatted": "NGN5000"
      },
      "reference": "VA_A1B2C3D4E5F6",
      "walletId": "019a1639-ce25-70b7-ab20-16fcb2bac025",
      "sourceReference": "src_1234567890",
      "accountNumber": "1234567890",
      "accountName": "Acme Store",
      "providerName": "wema",
      "accountType": "single_use",
      "status": "active",
      "currency": "NGN",
      "expiresAt": "2026-07-20 18:30:00",
      "createdAt": "2026-07-20 09:15:00"
    }
  ],
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 3,
    "per_page": 50,
    "to": 50,
    "total": 128
  }
}

Retrieving a virtual account

You can retrieve a single virtual account by its Atlas-generated reference, or by your own source_reference, using the Get Virtual Account endpoint. Pass exactly one of reference or sourceReference as a query parameter, the only difference is the query string:

curl -X GET "https://atlas.tryduplo.com/api/v1/virtual-account/find?reference=VA_A1B2C3D4E5F6" \
  -H "Authorization: Bearer your_API_KEY" \
  -H "Accept: application/json"

The examples below use the reference lookup, swap the query parameter for the source reference variant above to look up by source_reference instead:

curl -X GET "https://atlas.tryduplo.com/api/v1/virtual-account/find?reference=VA_A1B2C3D4E5F6" \
  -H "Authorization: Bearer your_API_KEY" \
  -H "Accept: application/json"

If neither reference nor sourceReference is provided, or if both are provided, the request returns a 422. If no virtual account with that reference (or source reference) belongs to your business, the request returns a 404.

Reactivating an expired virtual account

A single_use virtual account provisioned with wema can be reactivated with a new expiry once it has expired, using the Update Virtual Account endpoint. All three conditions must hold:

  • The account is single_use.
  • It was provisioned with wema. globus and duplo accounts are fixed once created and can't be updated.
  • It has already expired.

Only expires_at is required, omitted optional fields keep their current value:

  • expires_at (required): the new expiry, formatted as ISO 8601 (YYYY-MM-DD HH:MM:SS), for example 2026-07-21 12:00:00. Must be in the future and within 24 hours.
  • account_name (optional): updates the display name.
  • wallet_id (optional): moves the account to a different collection wallet. Must belong to your business.
  • amount (optional): updates the fixed amount expected on the account.
curl -X POST "https://atlas.tryduplo.com/api/v1/virtual-account/update-by-reference/VA_A1B2C3D4E5F6" \
  -H "Authorization: Bearer your_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "expires_at": "2026-07-21 12:00:00"
  }'

On success, Atlas returns the updated virtual account with its new expiresAt and status reset to active:

JSON
{
  "requestId": "abcd1234-5678-90ef-ghij-1234567890kl",
  "requestTimestamp": "2026-07-20 09:15:00",
  "message": "Virtual account updated successfully.",
  "statusCode": 200,
  "data": {
    "id": "019a1639-ce25-70b7-ab20-16fcb2bac023",
    "businessId": "019a1639-ce25-70b7-ab20-16fcb2bac024",
    "businessState": "TEST",
    "amount": {
      "value": 5000,
      "currency": "NGN",
      "formatted": "NGN5000"
    },
    "reference": "VA_A1B2C3D4E5F6",
    "walletId": "019a1639-ce25-70b7-ab20-16fcb2bac025",
    "sourceReference": "src_1234567890",
    "accountNumber": "1234567890",
    "accountName": "Acme Store",
    "providerName": "wema",
    "accountType": "single_use",
    "status": "active",
    "currency": "NGN",
    "expiresAt": "2026-07-21 12:00:00",
    "createdAt": "2026-07-20 09:15:00"
  }
}

Note

If the account hasn't expired yet, or its provider doesn't permit updates, this request returns a 403. If it's a multi_use account, or the supplied wallet_id doesn't belong to your business, it returns a 422.

Getting virtual account transactions

To list the credits received by a virtual account, make a GET request to the Get Virtual Account Transactions endpoint with the account's reference. Only successful inflows credited to the account are returned.

Supported query parameters are startDate, endDate, limit (defaults to 1000), and page (defaults to 1).

Each transaction includes the sender's beneficiary details when available (beneficiarySenderName, beneficiarySenderAccountNumber, beneficiarySenderABankName), the paymentChannel used, and the wallet balance immediately after the credit was applied.

curl -X GET "https://atlas.tryduplo.com/api/v1/virtual-account/find-by-reference/VA_A1B2C3D4E5F6/transactions?limit=10&page=1" \
  -H "Authorization: Bearer your_API_KEY" \
  -H "Accept: application/json"

On a successful request, you'll get a response similar to the one below:

JSON
{
  "requestId": "abcd1234-5678-90ef-ghij-1234567890kl",
  "requestTimestamp": "2026-07-20 09:15:00",
  "message": "Virtual account transactions retrieved successfully.",
  "statusCode": 200,
  "data": {
    "totalAmountReceived": {
      "value": 5000,
      "currency": "NGN",
      "formatted": "NGN5000"
    },
    "data": [
      {
        "id": "019a1639-ce25-70b7-ab20-16fcb2bac025",
        "beneficiarySenderName": "John Doe",
        "beneficiarySenderAccountNumber": "0123456789",
        "beneficiarySenderABankName": "GTBank",
        "reference": "TRN_A1B2C3D4E5F6",
        "amount": {
          "value": 5000,
          "currency": "NGN",
          "formatted": "NGN5000"
        },
        "sessionId": "000016250720091500123456789012",
        "type": "credit",
        "currency": "NGN",
        "parentTransactionReference": null,
        "paymentChannel": "Bank Transfer",
        "balance": {
          "value": 5000,
          "currency": "NGN",
          "formatted": "NGN5000"
        },
        "status": "Successful",
        "createdAt": "2026-07-20 09:15:00"
      }
    ],
    "meta": {
      "current_page": 1,
      "from": 1,
      "last_page": 1,
      "per_page": 50,
      "to": 1,
      "total": 1
    }
  }
}

Handling virtual account deposits

When a payer deposits money into a virtual account, the amount is settled into your business wallet and Atlas sends an IN_FLOW_SUCCESS_EVENT webhook to your configured endpoint.

The recipient object on the webhook payload's data identifies the account that received the inflow:

recipient
{
  "accountName": "Acme Store",
  "accountNumber": "1234567890"
}

Match recipient.accountNumber against the accountNumber of your virtual accounts, available from the list or retrieve endpoints, to determine which account was credited. If the account is single_use, treat it as fulfilled once you receive this event, you can reactivate it later if you need to reuse it.

Always verify the webhook to ensure it came from Atlas.

How is this guide?

Last updated on

On this page