Skip to main content

Virtual Accounts

Virtual accounts are temporary bank accounts created specifically for individual transactions. They provide a secure way for customers to make payments directly from their bank accounts without sharing sensitive account details.

🏦 What are Virtual Accounts?

Virtual accounts are unique bank account numbers generated for specific transactions. Customers can transfer money to these accounts using their regular banking apps or internet banking platforms.

Key Features:

  • Unique Account Numbers: Each transaction gets a dedicated account number
  • Amount Validation: Flexible validation rules to ensure correct payment amounts
  • Automatic Expiration: Accounts expire after a specified time period
  • Real-time Notifications: Instant webhook notifications when payments are received

💰 Amount Validation Levels

Virtual accounts support flexible amount validation to control how customers can fund their accounts:

CodeValidation RuleDescriptionUse Case
A0Exact amount onlyAccount can only be funded with the exact amount specified during creationFixed-price products, exact payments
A1Less than specifiedAccount can only be funded with an amount less than what was specifiedPartial payments, discounts
A2Greater than specifiedAccount can only be funded with an amount greater than what was specifiedMinimum payments, tips
A3Equal or less thanAccount can be funded with the exact amount or any amount less than specifiedFlexible pricing, partial payments
A4Equal or greater thanAccount can be funded with the exact amount or any amount greater than specifiedMinimum payments, tips allowed
A5Any amountAccount can be funded with any amount (no validation)Donations, flexible payments

Examples:

// A0: Customer must pay exactly ₦5000
{
"amount": 500000, // ₦5000 in kobo
"amountValidation": "A0" // Exact amount only
}

// A1: Customer can pay less than ₦5000 (e.g., ₦4000, ₦3000)
{
"amount": 500000, // ₦5000 in kobo
"amountValidation": "A1" // Less than specified
}

// A2: Customer must pay more than ₦5000 (e.g., ₦6000, ₦7000)
{
"amount": 500000, // ₦5000 in kobo
"amountValidation": "A2" // Greater than specified
}

// A3: Customer can pay ₦5000 or less (e.g., ₦5000, ₦4000, ₦3000)
{
"amount": 500000, // ₦5000 in kobo
"amountValidation": "A3" // Equal or less than
}

// A4: Customer can pay ₦5000 or more (e.g., ₦5000, ₦6000, ₦7000)
{
"amount": 500000, // ₦5000 in kobo
"amountValidation": "A4" // Equal or greater than
}

// A5: Customer can pay any amount (e.g., ₦1000, ₦10000, ₦50000)
{
"amount": 500000, // ₦5000 in kobo (used as reference)
"amountValidation": "A5" // Any amount
}

⏰ Validity Time Options

Set how long virtual accounts remain active:

  • Minimum: 1 minute
  • Maximum: 4320 minutes (72 hours)
  • Default: 1440 minutes (24 hours)

🚀 Creating Virtual Accounts

Basic Virtual Account Creation

REST API

curl -X POST https://api.inpaycheckout.com/api/v1/developer/virtual-account \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"amount": 50000,
"name": "Order Payment",
"reference": "order-123",
"validityTime": 1440,
"amountValidation": "A0",
"metadata": {
"orderId": "ORDER-456",
"customerEmail": "customer@example.com",
"productName": "Premium Plan"
}
}'

GraphQL

mutation CreateVirtualAccount($input: CreateVirtualAccountInput!) {
createVirtualAccount(input: $input) {
success
message
data {
accountNumber
accountName
bankName
reference
amount
currency
expiresAt
validityTime
amountValidation
}
}
}

Variables:

{
"input": {
"amount": 50000,
"name": "Order Payment",
"reference": "order-123",
"validityTime": 1440,
"amountValidation": "A0",
"metadata": {
"orderId": "ORDER-456",
"customerEmail": "customer@example.com",
"productName": "Premium Plan"
}
}
}

Response Example

{
"success": true,
"message": "Virtual account created successfully",
"data": {
"accountNumber": "9876543210",
"accountName": "iNPAYCheckout-Order Payment",
"bankName": "VFD Microfinance Bank",
"reference": "order-123",
"amount": 50000,
"currency": "NGN",
"expiresAt": "2025-09-15T10:30:00.000Z",
"validityTime": 1440,
"amountValidation": "A1"
}
}

💻 Code Examples

JavaScript/Node.js

const axios = require('axios');

class VirtualAccountManager {
constructor(secretKey, baseUrl = 'https://api.inpaycheckout.com') {
this.secretKey = secretKey;
this.baseUrl = baseUrl;
this.headers = {
'Authorization': `Bearer ${secretKey}`,
'Content-Type': 'application/json'
};
}

// Create Virtual Account
async createVirtualAccount(amount, reference, options = {}) {
try {
const response = await axios.post(
`${this.baseUrl}/api/v1/developer/virtual-account`,
{
amount,
reference,
name: options.name || 'Payment',
validityTime: options.validityTime || 1440, // 24 hours
amountValidation: options.amountValidation || 'A0', // Exact amount
metadata: options.metadata || {}
},
{ headers: this.headers }
);

return response.data;
} catch (error) {
console.error('Error creating virtual account:', error.response?.data || error.message);
throw error;
}
}

// Get Transaction Status
async getTransactionStatus(reference) {
try {
const response = await axios.get(
`${this.baseUrl}/api/v1/developer/transaction/status`,
{
params: { reference },
headers: this.headers
}
);

return response.data;
} catch (error) {
console.error('Error getting transaction status:', error.response?.data || error.message);
throw error;
}
}

// Verify Transaction
async verifyTransaction(reference) {
try {
const response = await axios.post(
`${this.baseUrl}/api/v1/developer/transaction/verify`,
{ reference },
{ headers: this.headers }
);

return response.data;
} catch (error) {
console.error('Error verifying transaction:', error.response?.data || error.message);
throw error;
}
}
}

// Usage Examples
const virtualAccountManager = new VirtualAccountManager(process.env.INPAY_SECRET_KEY);

// E-commerce Order Payment
const orderPayment = await virtualAccountManager.createVirtualAccount(
50000, // ₦500.00
'order-123',
{
name: 'Premium Plan Purchase',
validityTime: 1440, // 24 hours
amountValidation: 'A0', // Exact amount only
metadata: {
orderId: 'ORDER-456',
customerEmail: 'customer@example.com',
productName: 'Premium Plan',
customerPhone: '+2348123456789'
}
}
);

console.log('Virtual Account Created:', orderPayment.data.accountNumber);

// Donation with minimum amount
const donation = await virtualAccountManager.createVirtualAccount(
10000, // ₦100.00 minimum
'donation-789',
{
name: 'Charity Donation',
validityTime: 2880, // 48 hours
amountValidation: 'A4', // Equal or greater than validation
metadata: {
campaignId: 'CAMP-123',
donorEmail: 'donor@example.com',
cause: 'Education Fund'
}
}
);

// Subscription with flexible amount
const subscription = await virtualAccountManager.createVirtualAccount(
25000, // ₦250.00 base price
'subscription-456',
{
name: 'Monthly Subscription',
validityTime: 720, // 12 hours
amountValidation: 'A5', // Any amount
metadata: {
planId: 'PLAN-PREMIUM',
customerEmail: 'subscriber@example.com',
billingCycle: 'monthly'
}
}
);

Python

import requests
import os

class VirtualAccountManager:
def __init__(self, secret_key, base_url='https://api.inpaycheckout.com'):
self.secret_key = secret_key
self.base_url = base_url
self.headers = {
'Authorization': f'Bearer {secret_key}',
'Content-Type': 'application/json'
}

def create_virtual_account(self, amount, reference, **options):
"""Create a virtual account"""
url = f"{self.base_url}/api/v1/developer/virtual-account"
data = {
'amount': amount,
'reference': reference,
'name': options.get('name', 'Payment'),
'validityTime': options.get('validityTime', 1440), # 24 hours
'amountValidation': options.get('amountValidation', 'A0'), # Exact amount
'metadata': options.get('metadata', {})
}

response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
return response.json()

def get_transaction_status(self, reference):
"""Get transaction status"""
url = f"{self.base_url}/api/v1/developer/transaction/status"
params = {'reference': reference}

response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
return response.json()

def verify_transaction(self, reference):
"""Verify transaction"""
url = f"{self.base_url}/api/v1/developer/transaction/verify"
data = {'reference': reference}

response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
return response.json()

# Usage Examples
virtual_account_manager = VirtualAccountManager(os.getenv('INPAY_SECRET_KEY'))

# E-commerce Order Payment
order_payment = virtual_account_manager.create_virtual_account(
50000, # ₦500.00
'order-123',
name='Premium Plan Purchase',
validityTime=1440, # 24 hours
amountValidation='A0', # Exact amount only
metadata={
'orderId': 'ORDER-456',
'customerEmail': 'customer@example.com',
'productName': 'Premium Plan',
'customerPhone': '+2348123456789'
}
)

print(f'Virtual Account Created: {order_payment["data"]["accountNumber"]}')

# Donation with minimum amount
donation = virtual_account_manager.create_virtual_account(
10000, # ₦100.00 minimum
'donation-789',
name='Charity Donation',
validityTime=2880, # 48 hours
amountValidation='A4', # Equal or greater than validation
metadata={
'campaignId': 'CAMP-123',
'donorEmail': 'donor@example.com',
'cause': 'Education Fund'
}
)

# Subscription with flexible amount
subscription = virtual_account_manager.create_virtual_account(
25000, # ₦250.00 base price
'subscription-456',
name='Monthly Subscription',
validityTime=720, # 12 hours
amountValidation='A5', # Any amount
metadata={
'planId': 'PLAN-PREMIUM',
'customerEmail': 'subscriber@example.com',
'billingCycle': 'monthly'
}
)

PHP

<?php
class VirtualAccountManager {
private $secretKey;
private $baseUrl;

public function __construct($secretKey, $baseUrl = 'https://api.inpaycheckout.com') {
$this->secretKey = $secretKey;
$this->baseUrl = $baseUrl;
}

private function makeRequest($method, $endpoint, $data = null) {
$url = $this->baseUrl . $endpoint;
$headers = [
'Authorization: Bearer ' . $this->secretKey,
'Content-Type: application/json'
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

if ($data && in_array($method, ['POST', 'PUT', 'PATCH'])) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode >= 400) {
throw new Exception('API Error: ' . $response);
}

return json_decode($response, true);
}

public function createVirtualAccount($amount, $reference, $options = []) {
$data = [
'amount' => $amount,
'reference' => $reference,
'name' => $options['name'] ?? 'Payment',
'validityTime' => $options['validityTime'] ?? 1440, // 24 hours
'amountValidation' => $options['amountValidation'] ?? 'A0', // Exact amount
'metadata' => $options['metadata'] ?? []
];

return $this->makeRequest('POST', '/api/v1/developer/virtual-account', $data);
}

public function getTransactionStatus($reference) {
$params = http_build_query(['reference' => $reference]);
return $this->makeRequest('GET', '/api/v1/developer/transaction/status?' . $params);
}

public function verifyTransaction($reference) {
return $this->makeRequest('POST', '/api/v1/developer/transaction/verify', [
'reference' => $reference
]);
}
}

// Usage Examples
$virtualAccountManager = new VirtualAccountManager($_ENV['INPAY_SECRET_KEY']);

// E-commerce Order Payment
$orderPayment = $virtualAccountManager->createVirtualAccount(
50000, // ₦500.00
'order-123',
[
'name' => 'Premium Plan Purchase',
'validityTime' => 1440, // 24 hours
'amountValidation' => 'A0', // Exact amount only
'metadata' => [
'orderId' => 'ORDER-456',
'customerEmail' => 'customer@example.com',
'productName' => 'Premium Plan',
'customerPhone' => '+2348123456789'
]
]
);

echo "Virtual Account Created: " . $orderPayment['data']['accountNumber'] . "\n";

// Donation with minimum amount
$donation = $virtualAccountManager->createVirtualAccount(
10000, // ₦100.00 minimum
'donation-789',
[
'name' => 'Charity Donation',
'validityTime' => 2880, // 48 hours
'amountValidation' => 'A4', // Equal or greater than validation
'metadata' => [
'campaignId' => 'CAMP-123',
'donorEmail' => 'donor@example.com',
'cause' => 'Education Fund'
]
]
);

// Subscription with flexible amount
$subscription = $virtualAccountManager->createVirtualAccount(
25000, // ₦250.00 base price
'subscription-456',
[
'name' => 'Monthly Subscription',
'validityTime' => 720, // 12 hours
'amountValidation' => 'A5', // Any amount
'metadata' => [
'planId' => 'PLAN-PREMIUM',
'customerEmail' => 'subscriber@example.com',
'billingCycle' => 'monthly'
]
]
);
?>

🎯 Use Case Examples

1. E-commerce Store

// Create virtual account for order payment
const orderPayment = await virtualAccountManager.createVirtualAccount(
order.totalAmount * 100, // Convert to kobo
`order-${order.id}`,
{
name: `${order.productName} Purchase`,
validityTime: 1440, // 24 hours
amountValidation: 'A0', // Exact amount only
metadata: {
orderId: order.id,
customerEmail: order.customerEmail,
productName: order.productName,
shippingAddress: order.shippingAddress
}
}
);

// Send account details to customer
await sendPaymentInstructions(customer.email, {
accountNumber: orderPayment.data.accountNumber,
accountName: orderPayment.data.accountName,
bankName: orderPayment.data.bankName,
amount: order.totalAmount,
reference: orderPayment.data.reference
});

2. Donation Platform

// Create donation virtual account
const donation = await virtualAccountManager.createVirtualAccount(
campaign.minimumDonation * 100, // Convert to kobo
`donation-${Date.now()}`,
{
name: `${campaign.name} Donation`,
validityTime: 2880, // 48 hours
amountValidation: 'A4', // Equal or greater than validation
metadata: {
campaignId: campaign.id,
donorEmail: donor.email,
cause: campaign.cause,
donorName: donor.name
}
}
);

// Display to donor
const donationPage = {
accountNumber: donation.data.accountNumber,
accountName: donation.data.accountName,
bankName: donation.data.bankName,
minimumAmount: campaign.minimumDonation,
campaignName: campaign.name,
expiresAt: donation.data.expiresAt
};

3. Subscription Service

// Create subscription payment account
const subscription = await virtualAccountManager.createVirtualAccount(
plan.price * 100, // Convert to kobo
`subscription-${subscriptionId}`,
{
name: `${plan.name} Subscription`,
validityTime: 720, // 12 hours
amountValidation: 'A0', // Exact amount only
metadata: {
planId: plan.id,
customerEmail: customer.email,
billingCycle: plan.billingCycle,
customerId: customer.id
}
}
);

// Send to customer
await sendSubscriptionPaymentLink(customer.email, {
planName: plan.name,
accountNumber: subscription.data.accountNumber,
accountName: subscription.data.accountName,
bankName: subscription.data.bankName,
amount: plan.price,
billingCycle: plan.billingCycle
});

🔍 Transaction Monitoring

Check Transaction Status

curl -X GET "https://api.inpaycheckout.com/api/v1/developer/transaction/status?reference=order-123" \
-H "Authorization: Bearer sk_live_your_secret_key"

Response:

{
"success": true,
"message": "Transaction status retrieved successfully",
"data": {
"reference": "order-123",
"type": "virtual_account",
"accountNumber": "9876543210",
"status": "completed",
"verified": true,
"customerEmail": "customer@example.com",
"customerName": "John Doe",
"createdAt": "2025-09-14T10:00:00.000Z",
"updatedAt": "2025-09-14T10:15:00.000Z"
}
}

Verify Transaction

curl -X POST https://api.inpaycheckout.com/api/v1/developer/transaction/verify \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"reference": "order-123"
}'

🚨 Error Handling

Common Errors

Invalid Amount

{
"success": false,
"message": "Amount is below minimum (₦100)",
"code": "INVALID_AMOUNT"
}

Invalid Validity Time

{
"success": false,
"message": "Validity time exceeds maximum (4320 minutes)",
"code": "INVALID_VALIDITY_TIME"
}

Virtual Account Creation Failed

{
"success": false,
"message": "Failed to create virtual account with VFD",
"code": "VIRTUAL_ACCOUNT_CREATION_FAILED"
}

Error Handling Example

try {
const virtualAccount = await virtualAccountManager.createVirtualAccount(
amount,
reference,
options
);

console.log('Virtual Account Created:', virtualAccount.data.accountNumber);
} catch (error) {
if (error.response?.data?.code === 'INVALID_AMOUNT') {
console.log('Amount must be at least ₦100.00');
} else if (error.response?.data?.code === 'VIRTUAL_ACCOUNT_CREATION_FAILED') {
console.log('Failed to create virtual account, please try again');
} else {
console.error('Unexpected error:', error.message);
}
}

💡 Best Practices

✅ Do's

  • Set appropriate validity times: Balance between convenience and security
  • Use meaningful references: Include order IDs, customer info, etc.
  • Include comprehensive metadata: Add all relevant transaction details
  • Monitor expiration: Set up alerts for expiring accounts
  • Handle webhooks: Implement webhook endpoints for real-time updates
  • Verify transactions: Always verify completed transactions

❌ Don'ts

  • Don't reuse references: Each virtual account needs a unique reference
  • Don't set very short validity times: Give customers enough time to pay
  • Don't ignore webhooks: Webhooks provide the most reliable payment notifications
  • Don't skip verification: Always verify transactions before fulfilling orders
  • Don't store sensitive data: Keep account numbers secure

🔄 Webhook Integration

Virtual account payments trigger webhooks for real-time notifications:

{
"event": "payment.virtual_account.completed",
"timestamp": "2025-09-14T10:30:00.000Z",
"data": {
"reference": "order-123",
"accountNumber": "9876543210",
"accountName": "iNPAYCheckout-Order Payment",
"bankName": "VFD Microfinance Bank",
"amount": 50000,
"currency": "NGN",
"customerEmail": "customer@example.com",
"customerName": "John Doe",
"status": "completed",
"transactionId": "iNPAY-1234567890",
"fromAccount": "1234567890",
"fromClient": "John Sender",
"narration": "Payment for ORDER-123",
"completedAt": "2025-09-14T10:30:00.000Z",
"metadata": {
"orderId": "ORDER-456",
"productName": "Premium Plan"
}
}
}

Next Steps: