Bonaventure OgetoBy Bonaventure Ogeto|

Assigning Dedicated Virtual Accounts to Users at Signup

To assign a DVA at signup: create a Paystack customer when the user registers, immediately call the dedicated_account endpoint with the customer code, store the returned account number in your database, and display it to the user. Handle creation failures with a retry queue so users are not blocked from completing registration if the Paystack API is temporarily unavailable.

The Signup Flow

app.post('/api/register', async function(req, res) {
  var email = req.body.email;
  var firstName = req.body.firstName;
  var lastName = req.body.lastName;
  var phone = req.body.phone;
  var password = req.body.password;

  // Step 1: Create the user in your database
  var user = await db.users.create({
    email: email,
    first_name: firstName,
    last_name: lastName,
    phone: phone,
    password: hashPassword(password),
    dva_status: 'pending',
  });

  // Step 2: Create Paystack customer
  try {
    var customerResponse = 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 customerData = await customerResponse.json();
    var customerCode = customerData.data.customer_code;

    await db.users.update(user.id, {
      paystack_customer_code: customerCode,
    });

    // Step 3: Create DVA
    var dvaResponse = 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: 'wema-bank',
      }),
    });

    var dvaData = await dvaResponse.json();

    if (dvaData.status) {
      await db.users.update(user.id, {
        dva_account_number: dvaData.data.account_number,
        dva_bank_name: dvaData.data.bank.name,
        dva_account_name: dvaData.data.account_name,
        dva_status: 'active',
      });
    } else {
      // DVA creation failed - queue for retry
      await retryQueue.add('create-dva', { userId: user.id, customerCode: customerCode });
    }
  } catch (err) {
    // Paystack API error - queue for retry
    await retryQueue.add('create-dva', { userId: user.id });
  }

  // Return success regardless - user can start using the app
  res.json({ success: true, userId: user.id });
});

Making DVA Creation Non-Blocking

The Paystack API call takes time and can fail. If you block the registration on DVA creation, users will see a slow signup or get errors they do not understand. The better pattern: complete registration immediately and create the DVA in the background.

// Background job processor
async function processDvaCreation(job) {
  var userId = job.data.userId;
  var user = await db.users.findById(userId);

  if (user.dva_status === 'active') {
    return; // Already created
  }

  // Ensure Paystack customer exists
  var customerCode = user.paystack_customer_code;
  if (!customerCode) {
    var customerResponse = 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: user.email,
        first_name: user.first_name,
        last_name: user.last_name,
        phone: user.phone,
      }),
    });

    var customerData = await customerResponse.json();
    customerCode = customerData.data.customer_code;

    await db.users.update(userId, { paystack_customer_code: customerCode });
  }

  // Create DVA
  var dvaResponse = 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: 'wema-bank',
    }),
  });

  var dvaData = await dvaResponse.json();

  if (dvaData.status) {
    await db.users.update(userId, {
      dva_account_number: dvaData.data.account_number,
      dva_bank_name: dvaData.data.bank.name,
      dva_account_name: dvaData.data.account_name,
      dva_status: 'active',
    });

    // Notify the user
    await sendEmail(user.email, 'dva_ready', {
      accountNumber: dvaData.data.account_number,
      bankName: dvaData.data.bank.name,
    });
  } else {
    throw new Error('DVA creation failed: ' + dvaData.message);
    // Job framework will retry automatically
  }
}

User Experience Patterns

Immediate display: If DVA creation succeeds during registration, show the account number on the welcome screen. "Your personal account number is 0123456789 at Wema Bank. Transfer any amount to deposit into your wallet."

Pending state: If DVA creation is still processing, show a placeholder. "Your account number is being set up. We will notify you when it is ready. You can start exploring the app in the meantime."

Ready notification: When the background job completes, send a push notification, email, or SMS. "Your account number is ready! Transfer to 0123456789 at Wema Bank to make your first deposit."

Error state: If DVA creation fails permanently after retries, show a support contact. "We could not set up your account number. Please contact support." This should be rare.

// API endpoint to check DVA status
app.get('/api/my-dva', async function(req, res) {
  var user = await db.users.findById(req.userId);

  if (user.dva_status === 'active') {
    res.json({
      ready: true,
      accountNumber: user.dva_account_number,
      bankName: user.dva_bank_name,
      accountName: user.dva_account_name,
    });
  } else {
    res.json({
      ready: false,
      message: 'Your account number is being set up. Check back shortly.',
    });
  }
});

BVN Validation Before DVA Creation

If your Paystack account requires BVN validation before DVA creation, split the signup into two phases:

  1. Phase 1: Register - Collect email, name, phone, password. Create the user account. Create the Paystack customer.
  2. Phase 2: Verify identity - Collect BVN, validate with Paystack, wait for validation webhook, then create DVA.

This means the user can start using the app immediately but cannot deposit until their identity is verified and the DVA is created. This is standard practice for fintech apps that need KYC compliance.

Show the verification status clearly in the app: "Identity verification in progress" during validation, "Verification complete" when done, and the account number once the DVA is created.

DVA Assignment at Scale

If you expect thousands of signups per day, batch DVA creation and manage rate limits:

  • Rate limiting: Paystack has API rate limits. Do not fire 1,000 DVA creation requests simultaneously. Use a queue with concurrency limits.
  • Preferred bank distribution: If one partner bank hits capacity or has issues, distribute DVA creation across available banks.
  • Monitoring: Track DVA creation success rates. If failures spike, investigate before thousands of users are stuck without account numbers.
  • Backfill: If you launch DVA support after already having users, run a migration job that creates DVAs for existing users.

For the full DVA implementation guide, see dedicated virtual accounts complete implementation. For reconciling deposits, see reconciling DVA deposits.

Stay Up to Date

DVA features and requirements evolve as Paystack adds new partner banks and updates compliance requirements.

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

Key Takeaways

  • Create the Paystack customer and DVA as part of your user registration flow. The user gets their account number right after signing up.
  • Make DVA creation non-blocking. If the Paystack API is slow or down, complete the registration and create the DVA asynchronously.
  • Store DVA details in your database. Do not call Paystack every time you need to show the user their account number.
  • Use a retry queue for failed DVA creations. A background job retries until the DVA is created, then notifies the user.
  • Handle the edge case where a user registers but the DVA creation fails permanently. Show a message like "Your account number is being set up" until it is ready.
  • If BVN validation is required before DVA creation, split the flow: register first, collect BVN, validate, then create DVA.

Frequently Asked Questions

How long does DVA creation take?
DVA creation is usually instant or takes a few seconds. The Paystack API returns the account number in the response. In rare cases, there may be a delay if the partner bank system is under load.
Can I create DVAs for users who signed up before I added DVA support?
Yes. Run a migration script that creates Paystack customers and DVAs for existing users. Process them in batches with delays between batches to respect rate limits.
What if a user changes their email or name after signup?
The DVA account name is set at creation time based on the customer name you provided. Changing the name on your platform does not automatically update the DVA account name. You may need to update the Paystack customer record separately.
Can I let the user choose which bank their DVA is at?
Yes. You can offer a choice of preferred_bank values (e.g., wema-bank, providus-bank) during signup and pass the selection to the DVA creation call. Not all partner banks may be available in all regions.

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