Dynamic virtual account
Give your customer a one-time Paga account number for a single payment. They pay by bank transfer from any Nigerian bank, and the account closes once it’s paid or expires.
| Environment | Base URL |
|---|---|
| Sandbox | https://beta-collect.paga.com |
| Live | https://collect.paga.com |
Authenticate with HTTP Basic and a hash header, as in Authentication. Test it in Sandbox first with the bank transfer simulator.
How it works
Create a payment request
With paymentMethods set to ["BANK_TRANSFER"]. Paga returns a one-time account number.
Show the account number
With the bank name Paga and the amount to pay.
Your customer transfers
From their bank app, USSD or branch.
Paga notifies you
A callback reaches your callback URL as soon as the money arrives. Verify it, then fulfil.
1. Create a payment request
POST /paymentRequest
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(referenceNumber + amount + currency + payer.phoneNumber + payer.email + hashKey)
Content-Type: application/jsonSet paymentMethods to ["BANK_TRANSFER"]. Use an empty string for payer.email in the hash if you don’t send it.
{
"referenceNumber": "ORD-2026-0187",
"amount": 50,
"currency": "NGN",
"payer": {
"name": "Bello Ramon",
"phoneNumber": "08064343187"
},
"payee": { "name": "Your business name" },
"expiryDateTimeUTC": "2026-11-10T15:35:00",
"isSuppressMessages": true,
"payerCollectionFeeShare": 1,
"payeeCollectionFeeShare": 0,
"isAllowPartialPayments": false,
"isAllowOverPayments": false,
"callBackUrl": "https://yourstore.com/webhooks/paga",
"paymentMethods": ["BANK_TRANSFER"]
}All request parameters (15)
| Parameter | Required | Description |
|---|---|---|
referenceNumber | Yes | Your unique reference. Use it to match callbacks and check status. Hash value 1 |
amount | Yes | Amount to request, in naira. Hash value 2 |
currency | Yes | NGN. Hash value 3 |
payer.name | Yes | Customer’s name |
payer.phoneNumber | Yes | Customer’s phone number. Hash value 4 |
payer.email | No | Customer’s email address. Hash value 5 |
payee.name | Yes | Your business name. Customers see it when they check the account name |
payerCollectionFeeShare | Yes | Share of the fee your customer pays, from 0.0 to 1.0 |
payeeCollectionFeeShare | Yes | Share of the fee you pay. Both shares must add up to 1.0 |
expiryDateTimeUTC | No | When the request expires, in UTC, up to one week ahead. A default applies if you leave it out |
isSuppressMessages | No | true stops Paga emailing or texting your customer. Default false |
callBackUrl | Yes | Where Paga sends the callback |
isAllowPartialPayments | No | true accepts payments below the amount. Default false |
isAllowOverPayments | No | true accepts payments above the amount |
paymentMethods | Yes | ["BANK_TRANSFER"] |
{
"referenceNumber": "ORD-2026-0187",
"statusCode": "0",
"statusMessage": "success",
"requestAmount": 50,
"totalPaymentAmount": 50.41,
"paymentMethods": [
{
"name": "BANK_TRANSFER",
"properties": {
"bankName": "Paga",
"AccountNumber": "3080242757"
}
}
],
"expiryDateTimeUTC": "2026-11-10T15:35:00",
"isPayerPagaAccountHolder": false,
"bankName": "Paga"
}Response fields (11)
| Field | What it’s for |
|---|---|
referenceNumber | Your reference from the request |
statusCode | 0 means the account number was created |
statusMessage | Readable status |
requestAmount | The amount you asked for |
totalPaymentAmount | The amount your customer pays, including any fee you pass on. Show this one |
paymentMethods[].name | BANK_TRANSFER, the method you requested |
paymentMethods[].properties.AccountNumber | The one-time account number to show your customer |
paymentMethods[].properties.bankName | Always Paga. Show it as the bank name |
expiryDateTimeUTC | When the account number stops accepting payments, in UTC |
isPayerPagaAccountHolder | true if the payer’s phone number or email belongs to a Paga account |
bankName | The bank to show your customer: Paga |
2. Show your customer the account number
Display the account number with the bank name Paga, the amount, and when it expires:
| Account number | 3080242757 |
| Bank | Paga |
| Account name | Your business name |
| Amount | ₦50.41 |
| Expires | 10 Nov 2026, 4:35 pm (Lagos time) |
Show totalPaymentAmount, not your requested amount, if your customer pays the fee.
3. Handle the callback
Paga sends this to your callback URL when the customer pays. Check the hash, respond with HTTP 200, then fulfil the order.
{
"event": "PAYMENT_COMPLETE",
"notificationId": "a12ef82b-14d0-4a67-a08c-162b95154f59",
"statusCode": "0",
"statusMessage": "Payment Request has been authorized",
"externalReferenceNumber": "ORD-2026-0187",
"state": "CONSUMED",
"outstandingBalance": 0,
"paymentAmount": 50.41,
"cumulativePaymentAmount": 50.41,
"collectionFee": 0.41,
"fundingDetails": {
"payerName": "Bello Ramon",
"payerAccountNumber": "0980763285",
"payerBankAccountNumber": "0980763285"
},
"hash": "33f9c9bd0785c2ccfa66ddedfbae7c3b56361250c581a39edad1d2ed08ccfbed845f34b8c30532f7c0ec544483ebee9360c5749bfe5eaa9986ac5553488e058a"
}Callback fields (10)
| Field | Meaning |
|---|---|
event | PAYMENT_COMPLETE when paid. You’ll also get a final callback if the request expires or is cancelled |
statusCode | 0 means successful |
externalReferenceNumber | Your referenceNumber, to match the callback to your order |
state | CONSUMED once fully paid |
paymentAmount | Amount received in this payment |
cumulativePaymentAmount | Total received so far. Useful when partial payments are allowed |
outstandingBalance | Amount still to be paid |
collectionFee | Paga’s fee for the collection |
fundingDetails | Who paid: payer name and account details |
hash | Signature to check before you trust the callback |
Check the callback hash
Join notificationId + statusCode + externalReferenceNumber + state + outstandingBalance + hashKey, then hash the result with SHA-512 and compare it with hash.
- Format
outstandingBalancewith two decimal places and no commas, such as0.00. If it’snull, leave it out. - Paga retries a callback up to three times, with the same
notificationId, until you respond with HTTP 200. De-duplicate on it.
Not paid? If the request expires or the customer cancels, Paga sends a final callback so you can close the order. You don’t need to keep checking.
4. Check payment status
Use the status endpoint to confirm a payment if a callback hasn’t arrived, or to double-check before you fulfil a large order.
POST /status
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(referenceNumber + hashKey)
Content-Type: application/json{
"referenceNumber": "ORD-2026-0187"
}Request parameters (1)
| Parameter | Required | Description |
|---|---|---|
referenceNumber | Yes | The referenceNumber you used when you created the payment request. Hash value 1 |
{
"referenceNumber": "ORD-2026-0187",
"statusCode": "0",
"statusMessage": "success",
"data": {
"referenceNumber": "ORD-2026-0187",
"statusCode": "0",
"statusMessage": "success",
"requestAmount": 50.0,
"totalPaymentAmount": 50.0,
"expiryDateTimeUTC": "2026-11-10T15:35:00",
"additionalData": {}
}
}Response fields (10)
| Field | Meaning |
|---|---|
referenceNumber | The payment request you asked about |
statusCode | 0 means your status request worked. It doesn’t tell you whether the payment was made |
statusMessage | Readable status of your status request |
data.referenceNumber | The payment request you asked about |
data.statusCode | The payment’s own status. 0 means paid. See Collect API status codes for the others |
data.statusMessage | Readable status of the payment |
data.requestAmount | The amount you asked for |
data.totalPaymentAmount | The amount your customer pays, including any fee you pass on |
data.expiryDateTimeUTC | When the payment request expires, in UTC |
data.additionalData | Extra details for some payment methods. Often empty |
Read data.statusCode, not the outer statusCode. The outer statusCode only says whether your status request worked. Fulfil the order only when data.statusCode is 0.
Don’t poll on a timer. Wait for the callback, and only check status if it hasn’t arrived. If you do check, wait a few seconds and double the wait each time, as in Best practices.
Good to know
Name enquiry still works after expiry
Customers and banks can look up the account name even after it expires, which makes payments easier to trace.
Underpayments and overpayments
Use isAllowPartialPayments and isAllowOverPayments to decide what happens when the amount doesn’t match.
Refunds
Refund all or part of a payment, or cancel an unpaid request. See Refunds.
Repeat customers?
Use Subsidiary Accounts for a permanent account number per customer.
Need help? We’re here 24/7
Talk to our support team any time, or ask questions in the developer community.
Updated about 21 hours ago

