Bonaventure OgetoBy Bonaventure Ogeto|

Paystack Dedicated Virtual Accounts: Complete Implementation

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.

What Is a Dedicated Virtual Account?

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.

Creating a Dedicated Virtual Account

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"

Handling Deposits via Webhooks

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.

Displaying Account Details to Customers

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.

BVN and KYC Requirements

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.

Deactivating and Managing DVAs

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.

Stay Up to Date

Paystack expands DVA availability and features periodically. We update these guides when things change.

Sign up for the McTaba newsletter to stay informed about Paystack DVA updates.

Key Takeaways

  • A Dedicated Virtual Account (DVA) is a permanent bank account number assigned to a specific customer on your platform.
  • When anyone transfers to a DVA, the funds go to your Paystack balance, and you receive a webhook identifying the customer and amount.
  • DVAs are available for NGN transactions through partner banks like Wema Bank and Providus Bank.
  • Creating a DVA requires a Paystack customer code. You can optionally provide the customer BVN for additional verification.
  • DVAs are ideal for wallet top-ups, marketplace deposits, savings platforms, and any flow where customers deposit money repeatedly.
  • You must handle the dedicatedaccount webhook events to credit customer balances and reconcile deposits.

Frequently Asked Questions

Can a customer have multiple DVAs?
A customer can have DVAs at different partner banks. Whether you can create multiple DVAs at the same bank for the same customer depends on Paystack and the partner bank policies.
Is there a cost to create a DVA?
Check with Paystack for current pricing. DVA creation and maintenance may have associated costs that differ from standard transaction fees.
What happens if someone transfers to a DVA and the customer account is closed?
If you have deactivated the DVA, the transfer should be rejected. If the DVA is still active but the customer no longer has an account on your platform, you will still receive the webhook. Handle this edge case by flagging the deposit for manual review rather than silently discarding it.
Can DVAs accept transfers from any bank?
Yes. DVAs are regular bank account numbers at the partner bank. Any Nigerian bank can transfer to them using standard inter-bank transfers (NIP). The source bank does not matter.
How quickly are DVA deposits confirmed?
DVA deposits follow standard inter-bank transfer timing. Most transfers are confirmed within seconds to a few minutes. Paystack fires the webhook as soon as the deposit is confirmed.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon