Virtual Accounts (NGN)
Payments received by the merchant into their account are referred to as collections/payins. This section will show how they are supported by the 54Pay platform.
Our Virtual Account Generation APIs let you create Static or Dynamic virtual accounts for receiving payments. A Static virtual account does not expire and can accept any amount, making it ideal for recurring or flexible payments. A Dynamic virtual account is valid for only 30 minutes and can only receive the exact amount specified at creation, making it suitable for time-sensitive or one-off transactions. Currently, these APIs allow for funds collection using Virtual Accounts limited to Nigeria. Kindly visit here to learn more about request headers. Below are the sample requests, responses and base urls.
Authentication
Requires authentication using your Merchant Public Key encoded in Base64 format.
Header:
x-api-key: {{Base64(Merchant Public Key)}}
Generate Static Account (POST)
https://va.dev.mypaygate.co/api/v1/account/static/generate
Request Headers
| Header | Type | Required | Description |
|---|---|---|---|
| x-api-key | string | Yes | Base64-encoded Merchant Public Key |
| Content-Type | string | Yes | Must be application/json |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| reference | string | Yes | Unique merchant reference for this account |
| string | Yes | Customer email address | |
| currency | string | Yes | Currency code (e.g., "NGN") |
| source | string | Yes | Source identifier (e.g., "API") |
| firstName | string | No | Customer first name |
| lastName | string | No | Customer last name |
| webhookUrl | string | No | Custom webhook URL for payment notifications. If empty, notifications are sent to the dashboard-configured webhook URL |
- cURL Request
- Successful Response
curl --location ‘https://va.dev.mypaygate.co/api/v1/account/static/generate' --header 'x-api-key: {{base64PublicKey}}' --data-raw '{
"reference": "PG-STC-17866544767382",
"email": "example@youremail.com",
"currency": "NGN",
"source": "API",
"firstName": "Example Company", //optional
"lastName": "Nigeria Limited", //optional,
"webhookUrl": "https://webhook.site/708c7e7f-e111-4d25-af9f-e6ec97e92f58"
}’
{
"success": true,
"message": "Account generated successfully",
"responseData": {
"accountCreatedReference": "PG-STC-17866544767382",
"partnerRef": "STATIC|20250811072137|7F9F1B8248|1754896897515",
"channel": "Bank Transfer",
"bankName": "54Pay Bank PLC",
"bankCode": "",
"accountName": "Demo Static Account",
"accountNumber": "7080606072",
"accountStatus": "Active",
"accountType": "Static",
"expiry": 0,
"amount": 0,
"fee": 0,
"amountExpected": 0,
"dateCreated": "2025-08-11 08:21:37",
"dateExpired": null
}
}
Generate Dynamic Account (POST)
https://va.dev.mypaygate.co/api/v1/account/dynamic/generate
Request Headers
| Header | Type | Required | Description |
|---|---|---|---|
| x-api-key | string | Yes | Base64-encoded Merchant Public Key |
| Content-Type | string | Yes | Must be application/json |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| creationRef | string | Yes | Unique merchant reference for this account |
| firstName | string | Yes | Customer first name |
| lastName | string | Yes | Customer last name |
| amount | number | Yes | Exact amount to be received |
| string | Yes | Customer email address | |
| currency | string | Yes | Currency code (e.g., "NGN") |
| source | string | Yes | Source identifier (e.g., "API") |
| webhookUrl | string | No | Custom webhook URL for payment notifications. If empty, notifications are sent to the dashboard-configured webhook URL |
- cURL Request
- Successful Response
curl --location 'https://va.dev.mypaygate.co/api/v1/account/dynamic/generate' --header 'x-api-key: {{BASE64(MERCHANT_PUBLIC_KEY)
}}' --data-raw '{
"creationRef": "PG-STC-17678798676395",
"firstName": "Trevor",
"lastName": "Merryweather",
"amount": 10000,
"email": "example@youremail.com",
"currency": "NGN",
"source": "API",
"webhookUrl": "https://webhook.site/708c7e7f-e111-4d25-af9f-e6ec97e92f58"
}'
{
"success": true,
"message": "Account generated successfully",
"responseData": {
"accountCreatedReference": "PG-STC-17678798676394",
"partnerRef": "DYNAMIC|20250808111707|A8054494FF|1754648227523",
"channel": "Bank Transfer",
"bankName": "54Pay Bank PLC",
"bankCode": "999999",
"accountName": "Demo Dynamic Account",
"accountNumber": "8836873721",
"accountStatus": "Active",
"accountType": "Dynamic",
"expiry": 21,
"amount": 10180,
"fee": 180,
"amountExpected": 10180,
"dateCreated": "2025-08-08 11:17:07",
"dateExpired": "2025-08-08 11:38:07"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Indicates if the request was successful |
| message | string | Response message |
| responseData.accountCreatedReference | string | Account creation reference |
| responseData.partnerRef | string | Partner reference identifier |
| responseData.channel | string | Payment channel type |
| responseData.bankName | string | Name of the issuing bank |
| responseData.bankCode | string | Bank code |
| responseData.accountName | string | Virtual account name |
| responseData.accountNumber | string | Virtual account number for receiving payments |
| responseData.accountStatus | string | Current account status |
| responseData.accountType | string | Account type ("Dynamic") |
| responseData.expiry | string | Validity time in minutes |
| responseData.amount | number | Total amount including fees |
| responseData.fee | number | Transaction fee |
| responseData.amountExpected | number | Exact amount required for successful payment |
| responseData.dateCreated | string | Account creation timestamp |
| responseData.dateExpired | string | Account expiry timestamp |
Important Notes
- Exact Amount Requirement: For dynamic virtual accounts, the customer must pay the exact amountExpected value down to the decimal. Any deviation will result in transaction failure.
- 30-Minute Validity: The account expires 30 minutes after creation.
- Single-Use: Dynamic accounts are designed for one-time transactions.
Simulate Funding (POST)
https://va.dev.mypaygate.co/api/v1/account/funding/simulate
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| accountReference | string | Yes | Reference of the virtual account to fund |
| amount | string | Yes | Amount to credit to the account |
- cURL Request
curl --location 'https://va.dev.mypaygate.co/api/v1/account/funding/simulate' --data '{
"accountReference": "PG-STC-17866544767382",
"amount": 10180.00
}'
Important Notes
- This endpoint is available only in the sandbox environment for testing purposes.
- Use this to simulate customer payments.
Transaction Status Query (GET)
https://va.dev.mypaygate.co/api/v1/transaction/requery?reference=PG-STC-17678798676394
- cURL Request
- Successful
- Failed
curl --location 'https://va.dev.mypaygate.co/api/v1/transaction/requery?reference=PG-STC-32322442424&x-api-key={{Base64(MerchantPublicKey)}}'
{
"success": true,
"message": "Record found",
"responseData": {
"responseCode": "00",
"responseMessage": "COMPLETED",
"transactionStatus": "COMPLETED",
"amount": 10000,
"fee": 180,
"merchantReference": "PG-STC-17678798676394",
"transactionReference": "PG|20250808111819|29AEDAC209|1754648299342",
"channel": "Bank Transfer",
"description": "",
"dateStarted": "2025-08-08 11:17:08",
"dateCompleted": "2025-08-08 11:18:19"
}
}
{
"success": true,
"message": "Transaction not found",
"responseData": {
"responseCode": "25",
"responseMessage": "Transaction with reference: PG-STC-1767879867639 not found.",
"transactionStatus": null,
"amount": null,
"fee": null,
"merchantReference": null,
"transactionReference": null,
"channel": null,
"description": null,
"dateStarted": null,
"dateCompleted": null
}
}
Sample Webhooks
- Successful
{
"merchantReference": "PG-STC-4002865275",
"transactionReference": "PG-SAC|20251021221444|881D669889|1761084884636",
"transactionStatus": "COMPLETED",
"transactionFee": 100,
"amountReceived": 1417,
"initiatedDate": "2025-10-21 22:14:44.594314619",
"currentStatusDate": "2025-10-21 22:14:44.59433262",
"receivedFrom": {
"accountNumber": "8888878786",
"accountName": "Demo Funding",
"bankCode": "999999",
"bankName": "54Pay Bank PLC"
},
"status": "Funds Received",
"channel": "Bank Transfer",
"currencyCode": "NGN"
}
Webhook Security Headers
Our virtual accounts webhook requests include the following security headers for verification:
| Field | Description |
|---|---|
| sourceIp | IP address from which the webhook originates |
| transactionHash | HMAC-SHA512 hash of the webhook payload for verification |
Verifying Webhook Authenticity
To ensure webhook security, you must verify the transactionHash header against the payload received. This prevents unauthorized or tampered webhook requests.
Step 1: Sort Payload Keys
The payload must be sorted alphabetically by keys before hashing. Use a TreeMap or equivalent to ensure consistent key ordering.
Java Example:
private String generatePayloadHash(JsonObject payload) {
try {
String key = "{{merchant_secret_key}}";
Type treeMapType = new TypeToken<TreeMap<String, Object>>() {}.getType();
Map<String, Object> payloadMap = gson.fromJson(payload, treeMapType);
return HashingUtil.hashPayload(key, gson.toJson(payloadMap));
} catch (Exception e) {
return "Error";
}
}
Step 2: Generate HMAC-SHA512 Hash
Use your Merchant Secret Key to generate an HMAC-SHA512 hash of the sorted JSON payload.
Java Example:
public static String hashPayload(String key, String payload) {
try {
// Create a secret key object
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacSHA512");
// Create an HMAC-SHA512 instance and initialize it with the secret key
Mac mac = Mac.getInstance("HmacSHA512");
mac.init(keySpec);
// Generate the HMAC-SHA512 hash
byte[] hashBytes = mac.doFinal(payload.getBytes());
// Convert the hash bytes to hexadecimal format
StringBuilder hexHash = new StringBuilder();
for (byte b : hashBytes) {
String hex = String.format("%02x", b);
hexHash.append(hex);
}
// Return hashed value
return hexHash.toString();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
Step 3: Compare Hashes
Compare the generated hash with the value in the transactionHash header. If they match, the webhook is authentic.
Verification Example:
String receivedHash = request.getHeader("transactionHash");
String computedHash = generatePayloadHash(webhookPayload);
if (receivedHash != null && receivedHash.equals(computedHash)) {
// Webhook is authentic, process the payment
processPayment(webhookPayload);
} else {
// Invalid webhook, reject the request
throw new SecurityException("Invalid webhook signature");
}
Important Notes
- Specify a custom webhookUrl in the account creation request, or Configure a default webhook URL in your merchant dashboard
- Ensure your webhook endpoint can receive POST requests and returns a 200 OK response
- Always verify the transactionHash header before processing webhook data
- Optionally, whitelist the sourceIp for additional security
Base URLs
DEVELOPMENT: - https://va.dev.mypaygate.co
PRODUCTION: - https://va.prod.mypaygate.co