Dynamic virtual account

Bank transfer

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.

EnvironmentBase URL
Sandboxhttps://beta-collect.paga.com
Livehttps://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/json

Set 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)
ParameterRequiredDescription
referenceNumberYesYour unique reference. Use it to match callbacks and check status. Hash value 1
amountYesAmount to request, in naira. Hash value 2
currencyYesNGN. Hash value 3
payer.nameYesCustomer’s name
payer.phoneNumberYesCustomer’s phone number. Hash value 4
payer.emailNoCustomer’s email address. Hash value 5
payee.nameYesYour business name. Customers see it when they check the account name
payerCollectionFeeShareYesShare of the fee your customer pays, from 0.0 to 1.0
payeeCollectionFeeShareYesShare of the fee you pay. Both shares must add up to 1.0
expiryDateTimeUTCNoWhen the request expires, in UTC, up to one week ahead. A default applies if you leave it out
isSuppressMessagesNotrue stops Paga emailing or texting your customer. Default false
callBackUrlYesWhere Paga sends the callback
isAllowPartialPaymentsNotrue accepts payments below the amount. Default false
isAllowOverPaymentsNotrue accepts payments above the amount
paymentMethodsYes["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)
FieldWhat it’s for
referenceNumberYour reference from the request
statusCode0 means the account number was created
statusMessageReadable status
requestAmountThe amount you asked for
totalPaymentAmountThe amount your customer pays, including any fee you pass on. Show this one
paymentMethods[].nameBANK_TRANSFER, the method you requested
paymentMethods[].properties.AccountNumberThe one-time account number to show your customer
paymentMethods[].properties.bankNameAlways Paga. Show it as the bank name
expiryDateTimeUTCWhen the account number stops accepting payments, in UTC
isPayerPagaAccountHoldertrue if the payer’s phone number or email belongs to a Paga account
bankNameThe 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 number3080242757
BankPaga
Account nameYour business name
Amount₦50.41
Expires10 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)
FieldMeaning
eventPAYMENT_COMPLETE when paid. You’ll also get a final callback if the request expires or is cancelled
statusCode0 means successful
externalReferenceNumberYour referenceNumber, to match the callback to your order
stateCONSUMED once fully paid
paymentAmountAmount received in this payment
cumulativePaymentAmountTotal received so far. Useful when partial payments are allowed
outstandingBalanceAmount still to be paid
collectionFeePaga’s fee for the collection
fundingDetailsWho paid: payer name and account details
hashSignature 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 outstandingBalance with two decimal places and no commas, such as 0.00. If it’s null, 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)
ParameterRequiredDescription
referenceNumberYesThe 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)
FieldMeaning
referenceNumberThe payment request you asked about
statusCode0 means your status request worked. It doesn’t tell you whether the payment was made
statusMessageReadable status of your status request
data.referenceNumberThe payment request you asked about
data.statusCodeThe payment’s own status. 0 means paid. See Collect API status codes for the others
data.statusMessageReadable status of the payment
data.requestAmountThe amount you asked for
data.totalPaymentAmountThe amount your customer pays, including any fee you pass on
data.expiryDateTimeUTCWhen the payment request expires, in UTC
data.additionalDataExtra 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.