Virtual Account Creation


Create Virtual Accounts on demands, receive funds from customers via Virtual Account Number and get notification on Virtual Accounts usage.
Dedicated Virtual Accounts (DVAs) is a service that lets you create Nigerian virtual accounts for your customers. These accounts allow your customers to receive payment from their customers.
When you create a Dedicated Virtual Account (DVA) for a customer, all bank transfers to that account will automatically be recorded as transactions from that customer.


  • NGN

Integration steps

You can generate a VA and start receiving money in 3 easy steps:
  1. 1.
    Prepare payload (payload includes: first name, last name, BVN, and email)
  2. 2.
    Initiate VA creation (encrypting payload)
  3. 3.
    Get instant response

Before you begin!

  • Link to the Postman collection
  • Find your keys on the Klasha Dashboard → Settings → Generate API Keys (here)
  • Klasha Dashboard, Generate API Keys page
Generate bearer token
Using the Token endpoint on the Postman collection, the token can be obtained from the headers under the header name: token. See the screenshot below for an example:

Initiate VA creation

// Create virtual account
"firstName": "John",
"lastName": "Doe",
"bvn": "22222222222",
"email": "[email protected]"
Fields specification:
- "firstName" is the first name of the user
- "lastName" is the last name of the user
- "bvn" is the Bank Verification Number of the user
- "email" is the email address of the user
To create a virtual account using the Create virtual account API on the Postman collection, see the section below to get an example of the payload you need to send in order to create a transfer.
  • Serialise the payload
Encryption Algorithm
You need to encrypt the entire create virtual account payload that was specified on the previous point (here)
In order to encrypt the body correctly, you’d need the encryptionKey (that you can obtain following this guide here). We also provided an example in Java of the encryption algorithm in order to help you.
public static String encryptCBC(String stringToEncrypt, String encryptionkey) {
try {
SecureRandom sr = new SecureRandom();
byte[] salt = new byte[8];
final byte[][] keyAndIV =GenerateKeyAndIV(32, 16, 1, salt, password.getBytes(StandardCharsets.UTF_8),
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyAndIV[0], "AES"), new IvParameterSpec(keyAndIV[1]));
byte[] encryptedData = cipher.doFinal(stringToEncrypt.getBytes(StandardCharsets.UTF_8));
byte[] prefixAndSaltAndEncryptedData = new byte[16 + encryptedData.length];
// Copy prefix (0-th to 7-th bytes)
System.arraycopy("Salted__".getBytes(StandardCharsets.UTF_8), 0, prefixAndSaltAndEncryptedData, 0, 8);
// Copy salt (8-th to 15-th bytes)
System.arraycopy(salt, 0, prefixAndSaltAndEncryptedData, 8, 8);
// Copy encrypted data (16-th byte and onwards)
System.arraycopy(encryptedData, 0, prefixAndSaltAndEncryptedData, 16, encryptedData.length);
return Base64.getEncoder().encodeToString(prefixAndSaltAndEncryptedData);
} catch (Exception e) {
throw new RuntimeException(e);
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
generatedLength += digestLength;
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte) 0);
Encrypted payload
The result of the encryption of the payload that we specified has to be used with the Create virtual account endpoint as shown below:
Get instant response
When a VA creation is initiated, processing could take a few seconds. These are sample responses you will get below:
"id": 218,
"walletId": null,
"accountNumber": "8574578073",
"bankName": "WEMA BANK",
"orderRef": "fOeatZO40PEJzeiPvq8q",
"txRef": "klasha-virtual-account-banktransfer-Steph and sons-133",
"flwRef": "URF_G6Ie8ZP9GZ5hAZk8z6fZ",
"userId": null,
"businessId": 133,
"bankCode": null,
"enabled": true,
"accountName": "Stephen Ojerinde",
"email": "[email protected]",
"profileHash": "hwKpOI9+kZR/fFy7hLOpcQ==",
"createdAt": "2023-07-07 00:42:45",
"updatedAt": "2023-07-07 00:42:45"
"message": "There is no identity available for this bvn.",
"error": "Operation not allowed"
"message": "Bvn name mismatch.",
"error": "Operation not allowed"
In case of network downtime or failures during VA creation, the virtual account can be re-queried with the user’s email. This can be fetched using the Requery with email API on the Postman collection.
Transaction Webhook
When payments are received from your users, we would send a webhook with the details of the transaction to your callback URL.
"data": {
"createdAt": "2023-07-14T19:28:18.585",
"narration": "Bank Transfer",
"destinationCurrency": "NGN",
"sourceAmount": 2000,
"sourceCurrency": "NGN",
"tnxRef": "reference_322123243",
"status": "successful",
"destinationAmount": 2000,
"customer": {
"id": 53603,
"name": "Last Name",
"email": "[email protected]",
"phone": null,
"createdAt": "2023-07-04 20:11:05",
"updatedAt": "2023-07-04 20:11:05"
"event": "charge.completed"
Polling transaction status
To know the status of a transaction, you can fetch the transaction using the Transaction Status API on the Postman collection. Make use of the session id received from the bank. See an example below:
Last modified 2mo ago