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, orduplo. Test API keys may only useduplo; live API keys may only useglobusorwema. Onlywemaaccounts can be updated after creation,globusandduploaccounts 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 toNGNwhen omitted. Must be a currency currently enabled for virtual accounts on your business.expires_at(required forsingle_use, omit formulti_use): formatted as ISO 8601 (YYYY-MM-DD HH:MM:SS), for example2026-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:
{
"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:
| Parameter | Description |
|---|---|
reference | Filter by the Atlas-generated account reference. |
accountNumber | Filter by the account number. |
accountType | Filter by single_use or multi_use. |
status | Filter by derived status, active, inactive, or expired. An account past its expiry is always expired, even if this filter is set to active. |
currency | Filter by currency, for example NGN. |
search | Partial match on reference, account number, or account name. |
startDate / endDate | Filter by creation date range. |
sort | Sort direction on createdAt, ASC or DESC. Defaults to DESC. |
limit | Results per page, defaults to 1000. |
page | Page 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:
{
"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.globusandduploaccounts 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 example2026-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:
{
"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:
{
"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:
{
"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
Collection Workflow
Understand how inbound payments work on Atlas, including supported payment methods, fees, settlement, and how to verify payments.
Atlas Checkout
Create checkout URLs via the Atlas API to collect payments from customers, configure redirect URLs, and handle payment confirmation webhooks.