Viscount Payment APIs
Viscount Payment APIs provide a secure, scalable, and efficient foundation for businesses looking to modernise their payment infrastructure. They enable organisations to seamlessly collect, process, and manage payments while maintaining full control over financial operations — from virtual account creation and direct debit mandates to real-time money transfers across Nigerian banks.
Through easy integration, Viscount APIs allow payment capabilities to be embedded directly into your applications, delivering a smooth and reliable user experience. Designed for both startups and established enterprises, the platform offers a dependable, CBN-compliant foundation for scaling payment operations backed by a robust infrastructure and dedicated support team.
Getting Started
To integrate Viscount Payment APIs, you must complete a simple onboarding process to ensure compliance and secure access. This process involves creating a corporate account and completing Know Your Customer (KYC) verification before API credentials are issued.
Create a Corporate Account
Register your business through the corporate portal at corporate.viscountbank.com by providing accurate company details, including your business registration information, contact details, and authorised signatories. This establishes your organisation as an authorised entity on the Viscount platform and enables access to API credential management.
Complete KYC Verification
Submit the required documents for KYC verification, including your Certificate of Incorporation, board resolutions, identification documents for directors, and proof of business address. Once approved, your account will be activated and you will gain access to both the Sandbox (testing) and Live (production) environments along with your API credentials — separate Secret Keys for each environment.
API Environments
Viscount provides two distinct environments to support a robust development lifecycle. This separation ensures your integration is stable and thoroughly tested before processing real financial transactions in production.
Sandbox (Test)
The Sandbox environment is a fully functional replica of the Live environment that allows you to simulate all API features without any real financial impact. You can test complete payment flows, generate mock virtual account details, simulate transfers and direct debits, trigger specific error scenarios, and validate your webhook integrations. All responses mirror the exact format you will encounter in production, making the transition seamless. Sandbox API keys are issued separately from Live keys and are clearly identified in your Corporate Internet Banking portal.
Live (Production)
The Live environment is where real transactions are processed against actual bank accounts. Once you have completed thorough testing in the Sandbox and are confident that your integration handles all scenarios correctly — including error handling, webhook processing, and idempotency — you can switch to Live. All payments collected, transfers sent, and direct debits processed in this environment involve real funds and are subject to Central Bank of Nigeria regulatory requirements.
Security & Authentication
The Viscount Payment API employs a multi-layered authentication and validation framework designed to ensure the security, integrity, and reliability of all transactions. Every API request must satisfy all three security layers before it is processed.
Secret Key Authentication
All API requests must include a valid Secret Key in the Secret-Key request header. The Secret Key serves as the primary method of identifying and authorising API clients. Keys are issued through the Viscount Corporate Internet Banking portal and are unique to each client application. Separate keys are provided for Sandbox and Live environments — you must ensure the correct key is used for each environment. Secret Keys should be treated as highly sensitive credentials and stored securely on your server. Never expose them in client-side code, version control systems, or public repositories.
IP Authorisation
In addition to Secret Key authentication, Viscount enforces IP whitelisting across all environments. Only requests originating from pre-registered server IP addresses are accepted by the API. Any request from an unregistered IP address is automatically rejected with an appropriate error response. This significantly reduces the risk of unauthorised access, even in the event that your Secret Key is compromised. To register or update your whitelisted IPs, use the Corporate Internet Banking portal or contact your Viscount account manager.
Idempotency Control
Each API request must include a unique Request Key in the Request-Key header. This key enables idempotency control, ensuring that duplicate requests — whether caused by network timeouts, client retries, or connectivity issues — are processed only once. If the API receives a request with a Request Key that has already been used, it will return the original response without processing the request again. This prevents duplicate transactions and maintains data consistency across all operations. See the next section for details on constructing valid Request Keys.
Request Key Generation
The Request Key is a unique identifier included in every API request to enable idempotency control. It is constructed by concatenating five segments, producing a 99-character string that is virtually impossible to duplicate across requests.
Key Structure
| Segment | Length | Description |
|---|---|---|
| Timestamp | 14 characters | Current date and time formatted as YYYYMMDDHHmmss. Anchors the key to a specific moment in time. |
| Number Block | 28 characters | Seven randomly generated integers summing exactly to 1,000, each zero-padded to 4 digits. |
| Alpha Block 1 | 7 characters | Seven randomly selected uppercase ASCII letters (A–Z). |
| Digit Block | 30 characters | Thirty randomly generated single digits (0–9). |
| Alpha Block 2 | 20 characters | Twenty randomly selected uppercase ASCII letters (A–Z). |
Each Request Key must be unique. Reusing a key triggers the idempotency mechanism and returns the cached response from the original request.
Implementation — PHP
function generateRequestKey(): string
{
= date('YmdHis');
= [];
= 1000;
for ( = 0; < 6; ++) {
= min( - (6 - ), 9999);
= rand(1, );
[] = ;
-= ;
}
[] = ;
= implode('', array_map(
fn() => str_pad(, 4, '0', STR_PAD_LEFT),
));
= '';
for ( = 0; < 7; ++) .= chr(rand(65, 90));
= '';
for ( = 0; < 30; ++) .= rand(0, 9);
= '';
for ( = 0; < 20; ++) .= chr(rand(65, 90));
return . . . . ;
}
Implementation — JavaScript
function generateRequestKey() {
const now = new Date();
const pad = (n, l = 2) => String(n).padStart(l, '0');
const date = now.getFullYear()
+ pad(now.getMonth() + 1) + pad(now.getDate())
+ pad(now.getHours()) + pad(now.getMinutes())
+ pad(now.getSeconds());
const numbers = [];
let remaining = 1000;
for (let i = 0; i < 6; i++) {
const max = Math.min(remaining - (6 - i), 9999);
const n = Math.floor(Math.random() * max) + 1;
numbers.push(n);
remaining -= n;
}
numbers.push(remaining);
const numBlock = numbers.map(n => String(n).padStart(4, '0')).join('');
const randChar = () => String.fromCharCode(65 + Math.floor(Math.random() * 26));
const alpha1 = Array.from({ length: 7 }, randChar).join('');
const digits = Array.from({ length: 30 }, () => Math.floor(Math.random() * 10)).join('');
const alpha2 = Array.from({ length: 20 }, randChar).join('');
return date + numBlock + alpha1 + digits + alpha2;
}
Collecting Payment
Viscount enables you to collect payments through two primary channels: Bank Transfers (via virtual accounts) and Direct Debits. Both channels are accessible through the API, with real-time transaction notifications delivered via webhooks.
Bank Transfers
Generate dedicated virtual accounts for your customers to make payments directly into your Viscount account.
Static Virtual Accounts are permanent accounts assigned to individual customers. They can be used repeatedly for multiple transactions, making them ideal for recurring payments, subscription billing, and long-term customer relationships.
Dynamic Virtual Accounts are single-use accounts generated for a specific transaction. Once payment is received, the account is automatically closed. Suited for one-off payments such as invoice settlements, e-commerce checkout flows, and payment links.
Direct Debits
Debit a customer’s bank account directly based on an authorised mandate.
One-Time Mandates authorise a single debit of a specified amount. The mandate expires automatically once the transaction is completed.
Recurring Mandates authorise repeated debits over a defined period with a set amount per debit. Ideal for loan repayments, subscription billing, instalment payments, and scheduled collections where the customer has granted standing authorisation.
Making Payments
The Viscount Payment API enables you to send money to any bank account in Nigeria — whether to another Viscount account (internal transfer) or to any other commercial bank (external transfer via NIBSS).
Single Transfers allow you to initiate individual payments in real time. Each transfer is processed instantly and assigned a unique reference for tracking. You can optionally flag a transfer as requiring approval before processing, enabling a maker-checker workflow.
Bulk Transfers allow you to send payments to multiple recipients in a single API call by creating a transfer batch. Ideal for salary disbursements, vendor payments, and commission payouts. Each transfer within a batch is tracked individually, and you can approve or cancel an entire batch before processing. All transfers support full verification, real-time status tracking, and webhook notifications upon completion.
Response Format
The Viscount Payment API returns consistent, predictable JSON responses. There are two possible HTTP status codes, each with a defined structure.
Returns status (always "success"), message, and data (the response payload).
{
"status": "success",
"message": "Successful",
"data": {
"reference": "VST20260411143200",
"transaction_id": "TXN_0012345",
"amount": 50000
}
}Returns only status (always "error") and message. No data field.
{
"status": "error",
"message": "Invalid source account number."
}Webhooks
Webhooks allow Viscount to notify your application in real time when events occur on your account. Rather than polling the API for status updates, your server receives an immediate POST notification.
Configure your webhook URL through the Corporate Internet Banking portal. When an event occurs, Viscount sends a POST request with a JSON body containing the event type, associated data, and a timestamp.
Supported Events
| Event | Description |
|---|---|
| transaction-new | A successful credit is received on any account, including virtual accounts. Use this for real-time payment reconciliation. |
| transfer-successful | An outbound money transfer has completed successfully. Use this to confirm funds delivery. |
Example Payloads
Successful Credit
{
"event_type": "transaction-new",
"data": {
"transaction_id": "TXN_0012345",
"account_number": "82791089010",
"reference": "de92ui22",
"transaction_type": "CREDIT"
},
"timestamp": "2026-04-11T14:23:45+00:00"
}
Successful Transfer
{
"event_type": "transfer-successful",
"data": { "reference": "de92ui22" },
"timestamp": "2026-04-11T14:23:45+00:00"
}
Webhook Security
Set a secret hash stored as an environment variable. Viscount signs each payload with HMAC-SHA256 using this hash. The signature is included in the X-Viscount-Signature header. Compute the hash of the raw body and compare.
Verification — Python (Flask)
import hmac, hashlib, os
secret = os.environ['VISCOUNT_WEBHOOK_SECRET']
payload = request.get_data()
signature = request.headers.get('X-Viscount-Signature', '')
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401)
event = request.get_json()
Verification — Node.js (Express)
const crypto = require('crypto');
const secret = process.env.VISCOUNT_WEBHOOK_SECRET;
const signature = req.headers['x-viscount-signature'];
const expected = crypto.createHmac('sha256', secret)
.update(req.rawBody).digest('hex');
if (signature !== expected)
return res.status(401).send('Invalid signature');
const event = JSON.parse(req.rawBody);
200 immediately to acknowledge receipt. Process time-consuming logic asynchronously. Always verify using the raw request body — re-encoding JSON may alter key ordering.Try the API in Postman
All endpoints are documented and ready to test. Import the collection with one click — pre-configured headers, example payloads, and environment variables included.
Open Postman Collection →Virtual Accounts
Create, update, and query virtual accounts for payment collection. All requests are POST to /virtual-accounts.
Creates a new virtual account under your corporate account. The account name you provide will be prefixed with your registered business name automatically.
| Parameter | Description |
|---|---|
| actionstring | Fixed: CREATE_ACCOUNT |
| parent_account_numberstring | Business account number. For dynamic accounts, this is the settlement account. |
| account_namestring | Account name. Business prefix appended automatically. |
| account_type_codestring | VIRTUAL_ACCOUNT (static) or VIRTUAL_ACCOUNT_DYNAMIC (single-use). |
{ "action": "CREATE_ACCOUNT", "parent_account_number": "1000211487", "account_name": "ADEDEJI AWOLOLA", "account_type_code": "VIRTUAL_ACCOUNT" }Activate or deactivate an existing virtual account.
| Parameter | Description |
|---|---|
| actionstring | Fixed: UPDATE_ACCOUNT_NUMBER |
| account_numberstring | The virtual account number to update. |
| statusstring | ACTIVE or INACTIVE. |
{ "action": "UPDATE_ACCOUNT_NUMBER", "account_number": "1000211576", "status": "INACTIVE" }Retrieve account information and transaction history for a date range.
| Parameter | Description |
|---|---|
| actionstring | Fixed: ACCOUNT_ENQUIRY |
| account_numberstring | Account number to query. |
| start_datestring | Start date (YYYY-MM-DD). |
| end_datestring | End date (YYYY-MM-DD). |
{ "action": "ACCOUNT_ENQUIRY", "account_number": "1000211576", "start_date": "2026-01-01", "end_date": "2026-01-31" }Transactions
Verify the status and details of any transaction. Essential for confirming payments, resolving disputes, and maintaining records.
Verifies a transaction using its ID, reference, or both.
| Parameter | Description |
|---|---|
| actionstring | Fixed: VERIFY_TRANSACTION |
| account_numberstring | Account associated with the transaction. |
| transaction_idstring | Transaction ID assigned by Viscount. |
| referencestring | Transaction reference. |
{ "action": "VERIFY_TRANSACTION", "account_number": "1000211576", "transaction_id": "20260402195705CDLCCWDWKJ", "reference": "000966444324566658283981389895" }Money Transfer
Name lookups, single transfers, batch payments, and transfer status tracking. All requests are POST to /money-transfer.
Look up an account holder name before transferring.
| Parameter | Description |
|---|---|
| actionstring | Fixed: NAME_ENQUIRY |
| account_numberstring | Account to look up. |
| account_typestring | VISCOUNT or OTHER_BANKS. |
| bank_codestring | CBN bank code (required for OTHER_BANKS). |
{ "action": "NAME_ENQUIRY", "account_number": "1000211487", "account_type": "VISCOUNT", "bank_code": "00057" }Initiate a single real-time transfer to any Nigerian bank account.
| Parameter | Description |
|---|---|
| actionstring | Fixed: SEND_MONEY |
| account_numberstring | Destination account. |
| account_typestring | VISCOUNT or OTHER_BANKS. |
| bank_codestring | CBN bank code (for OTHER_BANKS). |
| amountnumber | Amount in Naira. |
| subject_to_approvalstring | YES or NO. |
| from_account_numberstring | Source account to debit. |
| referencestring | Unique transaction reference. |
| narrationstring | Description or note. |
{ "action": "SEND_MONEY", "account_number": "1000211521", "account_type": "VISCOUNT", "amount": 1000, "subject_to_approval": "NO", "from_account_number": "1000211487", "reference": "678986568909MMKLOdPP", "narration": "Vendor payment" }Create a batch of transfers from a single source account. Must be approved before processing.
| Parameter | Description |
|---|---|
| actionstring | Fixed: CREATE_TRANSFER_BATCH |
| from_account_numberstring | Source account for all transfers. |
| batch_titlestring | Descriptive batch title. |
| transfersarray | Array of transfer objects with account_number, type, amount, narration, bank_code. |
{ "action": "CREATE_TRANSFER_BATCH", "from_account_number": "1000211487", "batch_title": "SALARY PAYMENT", "transfers": [{ "account_number": "1000211497", "type": "VISCOUNT", "amount": 8000, "narration": "SALARY", "bank_code": "" }] }Approve a pending batch for processing.
| Parameter | Description |
|---|---|
| actionstring | Fixed: APPROVE_TRANSFER_BATCH |
| batch_idstring | Batch ID to approve. |
{ "action": "APPROVE_TRANSFER_BATCH", "batch_id": "051126260411202537782393912540" }Cancel a pending batch before approval.
| Parameter | Description |
|---|---|
| actionstring | Fixed: CANCEL_TRANSFER_BATCH |
| batch_idstring | Batch ID to cancel. |
{ "action": "CANCEL_TRANSFER_BATCH", "batch_id": "051126260411202537782393912540" }Retrieve batch details and individual transfer statuses.
| Parameter | Description |
|---|---|
| actionstring | Fixed: GET_TRANSFER_BATCH |
| batch_idstring | Batch ID to retrieve. |
{ "action": "GET_TRANSFER_BATCH", "batch_id": "051126260411205427287611731074" }Check the processing status of a specific transfer by its reference.
| Parameter | Description |
|---|---|
| actionstring | Fixed: TRANSFER_STATUS |
| referencestring | Transfer reference to look up. |
{ "action": "TRANSFER_STATUS", "reference": "051126260411205427515805070443" }Direct Debits
Create mandates, execute debits, and manage mandate lifecycle. All requests are POST to /direct-debit.
Initiate a new direct debit mandate — one-time or recurring.
| Parameter | Description |
|---|---|
| actionstring | Fixed: INITIATE |
| account_numberstring | Account to be debited. |
| narrationstring | Mandate description. |
| amountnumber | Debit amount per transaction (Naira). |
| account_typestring | VISCOUNT or OTHER_BANKS. |
| settlement_account_numberstring | Viscount account for settlement. |
| bank_codestring | Bank code (for OTHER_BANKS). |
| typestring | ONE_TIME or RECURRING. |
| start_datestring | Start date for RECURRING (YYYY-MM-DD). |
| end_datestring | End date for RECURRING (YYYY-MM-DD). |
{ "action": "INITIATE", "account_number": "1000211487", "narration": "Monthly subscription", "amount": 5000, "account_type": "VISCOUNT", "settlement_account_number": "1000211545", "bank_code": "", "type": "ONE_TIME", "start_date": "", "end_date": "" }Execute a debit against an existing active mandate.
| Parameter | Description |
|---|---|
| actionstring | Fixed: DEBIT_MANDATE |
| mandate_idstring | Mandate ID to debit. |
| amountnumber | Amount to debit (Naira). |
{ "action": "DEBIT_MANDATE", "mandate_id": "1000211487", "amount": 5000 }Cancel an active mandate, permanently revoking debit authorisation.
| Parameter | Description |
|---|---|
| actionstring | Fixed: CANCEL_MANDATE |
| mandate_idstring | Mandate ID to cancel. |
{ "action": "CANCEL_MANDATE", "mandate_id": "1000211487" }Ready to Integrate?
Import the complete Viscount API collection into Postman to start testing all endpoints with pre-configured headers, example payloads, and environment variables.
Open Postman Collection →