Create a Paystack Dedicated Virtual Account by POSTing to /dedicated_account with a customer code, preferred_bank, and optional BVN. Paystack returns a permanent bank account number at a partner bank. When anyone transfers to that account, Paystack fires a dedicatedaccount.assign.success or charge.success webhook with the deposit details. Use this for wallet top-ups, marketplace deposits, and any flow where customers need a permanent account number.
A Dedicated Virtual Account (DVA) is a bank account number that Paystack creates at a partner bank (like Wema Bank or Providus Bank) and assigns to a specific customer on your platform. Unlike the temporary accounts generated during bank transfer checkout, DVAs are permanent. The customer can use the same account number for every deposit.
When someone transfers money to a DVA, the funds arrive in your Paystack balance, and Paystack tells you (via webhook) which customer the account belongs to and how much was deposited. Your code then credits the customer's internal balance, activates a service, or triggers whatever fulfillment logic you need.
DVAs are the foundation of wallet-based applications, marketplace deposit systems, and savings platforms built on Paystack.
First, create a Paystack customer (if you have not already). Then create the DVA for that customer.
// Step 1: Create a Paystack customer
async function createCustomer(email, firstName, lastName, phone) {
var response = await fetch('https://api.paystack.co/customer', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
first_name: firstName,
last_name: lastName,
phone: phone,
}),
});
var data = await response.json();
return data.data.customer_code; // e.g., 'CUS_xxxxxxxx'
}
// Step 2: Create a DVA for the customer
async function createDVA(customerCode, preferredBank) {
var response = await fetch('https://api.paystack.co/dedicated_account', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer: customerCode,
preferred_bank: preferredBank, // e.g., 'wema-bank' or 'providus-bank'
}),
});
var data = await response.json();
if (data.status) {
return {
accountName: data.data.account_name,
accountNumber: data.data.account_number,
bankName: data.data.bank.name,
};
}
throw new Error('Failed to create DVA: ' + data.message);
}
// Full flow
var customerCode = await createCustomer('ade@example.com', 'Ade', 'Ogunleye', '08012345678');
var dva = await createDVA(customerCode, 'wema-bank');
console.log('Account: ' + dva.accountNumber + ' at ' + dva.bankName);
// Show this to the customer: "Transfer to 0123456789 at Wema Bank"
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
When someone transfers to a DVA, Paystack fires a webhook. The exact event type may be charge.success with a dedicated_account channel or a dedicated account-specific event. Your webhook handler credits the customer balance:
app.post('/webhooks/paystack', async function(req, res) {
res.sendStatus(200);
var event = req.body;
if (event.event === 'charge.success') {
var data = event.data;
var customerCode = data.customer.customer_code;
var amount = data.amount; // In kobo
var reference = data.reference;
// Find the customer in your database
var user = await db.users.findByPaystackCode(customerCode);
if (user) {
// Credit the customer's wallet
await db.wallets.credit(user.id, amount, {
source: 'dva_deposit',
reference: reference,
});
console.log('Credited ' + amount + ' kobo to user ' + user.id);
} else {
console.log('Deposit from unknown customer: ' + customerCode);
// Log for manual review
}
}
});
Make your deposit handler idempotent. If the same webhook arrives twice (Paystack retries on failure), do not credit the customer twice. Check if the reference has already been processed before crediting.
After creating a DVA, store the account details in your database and display them to the customer whenever they want to deposit:
// Store DVA details after creation
await db.users.update(user.id, {
dva_account_number: dva.accountNumber,
dva_bank_name: dva.bankName,
dva_account_name: dva.accountName,
});
// Display in your app
app.get('/api/my-account-details', async function(req, res) {
var user = await db.users.findById(req.userId);
res.json({
accountNumber: user.dva_account_number,
bankName: user.dva_bank_name,
accountName: user.dva_account_name,
instructions: 'Transfer any amount to this account to top up your wallet.',
});
});
Include clear instructions: "Transfer to [Account Number] at [Bank Name]. Your wallet will be credited automatically." Add a copy button for the account number so the customer can easily paste it into their banking app.
Some DVA configurations require the customer's BVN (Bank Verification Number) for compliance. If required, you validate the customer's identity with Paystack before creating the DVA:
// Validate customer identity
async function validateCustomer(customerCode, bvn, bankCode, accountNumber) {
var response = await fetch('https://api.paystack.co/customer/' + customerCode + '/identification', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'bvn',
value: bvn,
country: 'NG',
bank_code: bankCode,
account_number: accountNumber,
}),
});
var data = await response.json();
return data;
}
BVN validation is asynchronous. Paystack verifies the BVN with NIBSS and notifies you via webhook when validation is complete. Only create the DVA after successful validation.
The KYC requirements depend on your Paystack account type, the partner bank, and regulatory requirements. Check with Paystack for the current requirements for your specific setup.
You can deactivate a DVA when a customer closes their account or when you need to stop accepting deposits for a specific customer:
// Deactivate a DVA
async function deactivateDVA(dedicatedAccountId) {
var response = await fetch(
'https://api.paystack.co/dedicated_account/' + dedicatedAccountId,
{
method: 'DELETE',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
return response.json();
}
// List all DVAs
async function listDVAs() {
var response = await fetch('https://api.paystack.co/dedicated_account', {
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
});
var data = await response.json();
return data.data;
}
After deactivation, transfers to that account number will be rejected. Make sure to communicate this to the customer and remove the account details from their profile in your app.
For reconciliation patterns and handling mismatched deposits, see reconciling DVA deposits. For assigning DVAs during user registration, see assigning DVAs at signup.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course