Subsidiary accounts
Paga accounts for your customers, fully managed by you. Issue, hold, and move money on behalf of your end users – all from your Paga business account.
Give each customer their own permanent Paga account number. Hold their money in a real account with its own balance, or pass every payment straight through to your Paga business account or bank.
| 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. Every account is a real Paga account with its own NUBAN and history, and you remain its legal owner. Only NGN is supported.
Choose how the account settles
You choose the type when you create the account, with the autoSweep setting. You can change it later.
Ledger-backed customer account
Money stays in the account, with its own balance. You can charge it, top it up and transfer between accounts.
Leave out autoSweep.
Virtual account (business settlement)
Every payment goes straight to your Paga business account. The account keeps no balance.
autoSweep.destination: VIRTUAL_ACCOUNT
Virtual account (bank settlement)
Every payment goes straight to a bank account you choose. The account keeps no balance.
autoSweep.destination: BANK
Key terms (5)
| Term | Meaning |
|---|---|
accountReference | Identifies the account. Your own unique ID, 11 to 30 characters. You set it at creation and can’t change it |
referenceNumber | Identifies the request. Use a new one for every call |
accountNumber | The 10-digit NUBAN Paga generates for the account. Give this to your customer |
accountIdentifier | The account an operation applies to. Pass the account’s accountReference |
iifiNumber | Your customer’s BVN in requests. In callbacks it’s called financialIdentificationNumber |
How it works
Create an account for your customer
Paga returns a permanent account number they can pay into at any time.
Your customer pays in
By bank transfer from any Nigerian bank, or from a Paga wallet.
Paga notifies you
A funding notification reaches your callback URL with the amount and who paid. Verify it, then credit your customer.
Move money if you hold funds
For ledger-backed accounts, charge, top up or transfer between accounts using the accountReference, and check balances and history.
1. Create an account
POST /subsidiary-accounts
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(referenceNumber + accountReference + iifiNumber + autoSweep.bankPublicId + autoSweep.destination + autoSweep.accountNumber + callbackUrl + hashKey)
Content-Type: application/jsonNote the order of autoSweep.bankPublicId then autoSweep.destination; update uses the reverse. Use an empty string for any of these you don’t send. Use a new referenceNumber and accountReference for every account: reusing either returns a duplicate request response, and no account is created.
Duplicate request response
{
"referenceNumber": "REF-100005550002",
"statusCode": "102",
"statusMessage": "Duplicate request. A request with reference number REF-100005550002 has already been processed"
}If you get 102 after a timeout, the account may already exist. Retrieve it by its accountReference before creating it again.
{
"referenceNumber": "REF-100005550002",
"accountReference": "CUST-000184-WALLET",
"accountName": "Bello Ramon",
"firstName": "Bello",
"lastName": "Ramon",
"phoneNumber": "08012345678",
"email": "[email protected]",
"iifiNumber": "22222132329",
"fundingTransactionLimit": 500000.00,
"fundingDailyLimit": 2000000.00,
"status": "ACTIVE"
}{
"referenceNumber": "REF-100005550003",
"accountReference": "CUST-000185-RENT",
"accountName": "Jane Doe",
"firstName": "Jane",
"lastName": "Doe",
"phoneNumber": "08012345679",
"email": "[email protected]",
"status": "ACTIVE",
"autoSweep": {
"destination": "VIRTUAL_ACCOUNT"
}
}{
"referenceNumber": "REF-100005550004",
"accountReference": "CUST-000186-FEES",
"accountName": "Alice Smith",
"firstName": "Alice",
"lastName": "Smith",
"phoneNumber": "08012345670",
"email": "[email protected]",
"status": "ACTIVE",
"autoSweep": {
"destination": "BANK",
"bankPublicId": "7131F94B-C0AC-43FB-BF6A-3407D27F1806",
"accountNumber": "0073141524"
}
}All request parameters (16)
| Parameter | Required | Description |
|---|---|---|
referenceNumber | Yes | Unique reference for this request. Hash value 1 |
accountReference | Yes | Your unique ID for the account, 11 to 30 characters. Hash value 2 |
accountName | Yes | Name shown on the account |
firstName | Yes | Customer’s first name |
lastName | Yes | Customer’s last name |
phoneNumber | One of | Customer’s phone number. Send this, email, or both |
email | One of | Customer’s email. Send this, phoneNumber, or both |
iifiNumber | No | Customer’s BVN. Recommended for fraud checks. Hash value 3 |
callbackUrl | No | Webhook URL for this account only. Overrides your global callback. Hash value 7 |
fundingDailyLimit | No | Most the account can receive in a day. Transfers above it are declined by the sender’s bank |
fundingTransactionLimit | No | Most the account can receive in one payment |
status | No | ACTIVE (default) or DISABLED. Disabled accounts can’t receive or move money |
autoSweep | No | Leave out for a ledger-backed account that holds funds |
autoSweep.destination | No | VIRTUAL_ACCOUNT to settle to your Paga business account, or BANK to settle to a bank. Hash value 5 |
autoSweep.bankPublicId | If BANK | The bank’s ID, from Get Banks (GET /banks). Hash value 4 |
autoSweep.accountNumber | If BANK | The bank account to settle to. Hash value 6 |
{
"referenceNumber": "REF-100005550002",
"statusCode": "0",
"statusMessage": "success",
"accountNumber": "0750529406",
"accountReference": "CUST-000184-WALLET",
"status": "ACTIVE",
"balance": 0.0,
"currency": "NGN"
}Response fields (8)
| Field | Meaning |
|---|---|
referenceNumber | Your request reference |
statusCode | 0 means the account was created |
statusMessage | Readable status |
accountNumber | The account number to give your customer |
accountReference | Your account ID |
status | ACTIVE or DISABLED |
balance | Always 0.0 when created |
currency | NGN |
2. Receive payments
When someone pays into the account, Paga sends a funding notification to the account’s callback URL, or to your global callback if the account doesn’t have one.
Content-Type: application/json
x-paga-hash-v3: the hash to check
x-paga-hash-v3-parameters: the fields used to build it, in order{
"statusCode": "0",
"statusMessage": "success",
"transactionReference": "MPVAT-P_20260621182337959_1097807834",
"fundingTransactionReference": "DFB-U_20260621182337653_1097807831",
"fundingPaymentReference": "000013203297",
"accountNumber": "0750529406",
"accountName": "Bello Ramon",
"financialIdentificationNumber": null,
"amount": 5000.00,
"clearingFeeAmount": 37.50,
"payerDetails": {
"paymentMethod": "BANK_TRANSFER",
"payerName": "OKOYE CHIZOBA",
"payerBankName": "GT Bank",
"payerBankAccountNumber": "0697122703"
},
"narration": "NIP Transfer to Your business - Bello Ramon",
"transactionDate": "2026-06-21T17:23:37.653"
}Funding notification fields (12)
| Field | Meaning |
|---|---|
statusCode | 0 means the payment was received |
transactionReference | Paga’s reference for the credit. Can be null |
fundingTransactionReference | Paga’s reference for the incoming transfer |
fundingPaymentReference | The sender’s original payment reference |
accountNumber | The subsidiary account that was paid |
accountName | Name on the account |
financialIdentificationNumber | Customer’s BVN, if you set it |
amount | Amount received |
clearingFeeAmount | Fee deducted. Can be null |
payerDetails | Who paid: method, name, bank and account number |
narration | The transfer’s narration |
transactionDate | When the payment arrived, in UTC |
Check the hash
The hash is sent only in the x-paga-hash-v3 header; the payload doesn’t include one. Build it from the fields named in x-paga-hash-v3-parameters, in that order, then add your hash key and hash with SHA-512. For funding notifications that’s:
transactionReference + transactionDate + amount + accountNumber + timestamp + hashKey
Use an empty string if transactionReference is null. Then respond with HTTP 200 and {"status": "SUCCESS"}.
3. Move money (ledger-backed accounts)
Accounts that hold funds can be charged, topped up and transferred between. Pass the account’s accountReference as {accountIdentifier}, and give each request its own referenceNumber. A retry with the same referenceNumber never moves money twice.
Ledger-backed accounts onlyCharge, top-up and transfer don’t work on virtual accounts (settles to Paga or to your bank), because they don’t hold a balance. The request is rejected, for example:
Top-up is not permitted on a subsidiary account configured for auto-sweep (bank or virtual account)
Moves money from the subsidiary account to your Paga business balance, for example a service fee.
POST /subsidiary-accounts/{accountIdentifier}/charge
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(referenceNumber + amount + currency + narration + hashKey)
Content-Type: application/json{
"referenceNumber": "REF-CHARGE-001",
"amount": 1000.00,
"currency": "NGN",
"narration": "Service fee"
}{
"referenceNumber": "REF-CHARGE-001",
"statusCode": "0",
"statusMessage": "success",
"transactionId": "MPVAT-P_20260610214236118_4577638_JX438_qznml",
"newBalance": 98000.0
}Moves money from your Paga business balance into the subsidiary account, for example a bonus or a refund to the customer’s wallet. Your balance must cover it.
POST /subsidiary-accounts/{accountIdentifier}/top-up
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(referenceNumber + amount + currency + narration + hashKey)
Content-Type: application/json{
"referenceNumber": "REF-TOPUP-001",
"amount": 500.00,
"currency": "NGN",
"narration": "Bonus credit"
}{
"referenceNumber": "REF-TOPUP-001",
"statusCode": "0",
"statusMessage": "success",
"transactionId": "MPVAT-P_20260610220000000_4577650_SMQDM_qznml",
"newBalance": 50500.0
}Moves money between two of your subsidiary accounts, for example when one customer pays another on your platform.
POST /subsidiary-accounts/transfer
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(referenceNumber + sourceAccountIdentifier + destinationAccountIdentifier + amount + currency + narration + hashKey)
Content-Type: application/json{
"referenceNumber": "REF-TRANSFER-001",
"sourceAccountIdentifier": "CUST-000184-WALLET",
"destinationAccountIdentifier": "CUST-000221-WALLET",
"amount": 1500.00,
"currency": "NGN",
"narration": "Payment for goods"
}{
"referenceNumber": "REF-TRANSFER-001",
"statusCode": "0",
"statusMessage": "success",
"transactionId": "MPVAT-P_20261103221151937_4621540_KWNPH_j7pq9",
"source": {
"accountIdentifier": "CUST-000184-WALLET",
"amount": 1500,
"newBalance": 49494.62
},
"destination": {
"accountIdentifier": "CUST-000221-WALLET",
"amount": 1500,
"newBalance": 1500
}
}Transfer response fields (10)
| Field | Meaning |
|---|---|
referenceNumber | Your request reference |
statusCode | 0 means the transfer completed |
statusMessage | Readable status |
transactionId | Paga’s ID for the transfer |
source.accountIdentifier | The accountReference money was taken from |
source.amount | Amount debited |
source.newBalance | Source account balance after the transfer |
destination.accountIdentifier | The accountReference money was sent to |
destination.amount | Amount credited |
destination.newBalance | Destination account balance after the transfer |
Charge, top-up and transfer parameters
| Parameter | Required | Description |
|---|---|---|
referenceNumber | Yes | Unique reference. Reuse it only to retry the same operation |
amount | Yes | Amount to move |
currency | Yes | NGN |
narration | No | Description. Use an empty string in the hash if you leave it out |
sourceAccountIdentifier | Transfer only | The accountReference of the account to take money from |
destinationAccountIdentifier | Transfer only | The accountReference of the account to send money to |
Charge, top-up and transfer notifications
When a charge, top-up or transfer completes, Paga sends a callback to the account’s callback URL. All three share the same fields; only the event and transaction type differ.
{
"event": "SUBSIDIARY_ACCOUNT_CHARGE_COMPLETE",
"notificationId": "913ee825-a17d-477c-8231-07dfb95442bb",
"statusCode": "0",
"statusMessage": "SUCCESS",
"externalReferenceNumber": "REF-CHARGE-001",
"transactionType": "DEBIT",
"transactionDate": "2026-11-03 22:13:53.977",
"accountReference": "CUST-000184-WALLET",
"totalDebitAmount": 25.0,
"fee": 0.0,
"amount": "25.00",
"accountNumber": "0750529406",
"hash": "148b4f9443fc937f3cfefd5eb6b015b8a6f9a4ea8d42185ce821ad8d057ce9a6ecdbc5a4d9df3039cdac0be087c3623fb24a42f999b258c9d8697574bb698088",
"timestamp": "2026-11-03T21:13:54.081104984"
}{
"event": "SUBSIDIARY_ACCOUNT_TOP_UP_COMPLETE",
"notificationId": "b639f3a1-e8e3-4fa3-a975-d833a48a8fbe",
"statusCode": "0",
"statusMessage": "SUCCESS",
"externalReferenceNumber": "REF-TOPUP-001",
"transactionType": "CREDIT",
"transactionDate": "2026-11-03 22:13:03.887",
"accountReference": "CUST-000184-WALLET",
"totalDebitAmount": 1000.0,
"fee": 0.0,
"amount": "1,000.00",
"accountNumber": "0750529406",
"hash": "6eaed2941cdf386a7492cb4f89e3434fa593ca9ba0702f9833f58d9d5aa52a2103cb4545064ed07f2e1bb09922d9292ebd42a731afc2fe192f7603c055c1adcf",
"timestamp": "2026-11-03T21:13:04.005946423"
}{
"event": "SUBSIDIARY_ACCOUNT_TRANSFER_COMPLETE",
"notificationId": "b4544cc4-21fb-4318-bb39-c62be074628f",
"statusCode": "0",
"statusMessage": "SUCCESS",
"externalReferenceNumber": "REF-TRANSFER-001",
"transactionType": "DEBIT",
"transactionDate": "2026-11-03 22:11:51.937",
"accountReference": "CUST-000184-WALLET",
"totalDebitAmount": 500.0,
"fee": 0.0,
"amount": "500.00",
"accountNumber": "0750529406",
"hash": "c9561b24c07b4cd4e523e9776ea37aaf298fcd53c4583dd81cbb5d443b0d491253e2896c9d061f1a2bffcd53a03759a9ea466594ed2e33f85a4a8ac13779c3bd",
"timestamp": "2026-11-03T21:11:52.09496258"
}One callback per transfer. A transfer sends one DEBIT callback, for the source account. The destination account doesn’t get its own callback, so confirm its credit from destination.newBalance in the transfer response, or from the account’s history.
Notification fields (14)
| Field | Meaning |
|---|---|
event | SUBSIDIARY_ACCOUNT_CHARGE_COMPLETE, SUBSIDIARY_ACCOUNT_TOP_UP_COMPLETE or SUBSIDIARY_ACCOUNT_TRANSFER_COMPLETE |
notificationId | Unique ID for this notification. Retries use the same one, so de-duplicate on it |
statusCode | 0 means the operation completed |
statusMessage | SUCCESS |
externalReferenceNumber | The referenceNumber you sent with the request |
transactionType | DEBIT for charges and transfers (sent for the source account only), CREDIT for top-ups |
transactionDate | When the operation happened, in UTC |
accountReference | The account it applied to |
totalDebitAmount | amount plus fee |
fee | Processing fee. 0.0 if none |
amount | Net amount, as a formatted string. Use it exactly as received in the hash |
accountNumber | The account’s NUBAN |
hash | Signature to check before you trust the notification |
timestamp | When Paga sent the notification, in UTC |
Check the hash
Join externalReferenceNumber + transactionDate + amount + accountNumber + hashKey, hash the result with SHA-512, and compare it with hash.
Use each value exactly as it appears in the notification, joined with no separators. amount is a formatted string with commas, such as 1,000.00, so don’t convert it to a number before hashing. For the top-up example, the string to hash is REF-TOPUP-0012026-11-03 22:13:03.8871,000.000750529406 followed by your hash key.
Then respond with HTTP 200 and {"status": "SUCCESS"}.
4. Manage accounts
Every operation below takes the account’s accountReference as {accountIdentifier}, for example /subsidiary-accounts/CUST-000184-WALLET/balance.
Retrieve an account
GET /subsidiary-accounts/{accountIdentifier}?referenceNumber={referenceNumber}
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(referenceNumber + accountIdentifier + hashKey)Returns the account’s details, limits, callback URL and current balance.
{
"referenceNumber": "REF-GET-001",
"statusCode": "0",
"statusMessage": "success",
"accountNumber": "0750529406",
"accountReference": "CUST-000184-WALLET",
"status": "ACTIVE",
"balance": 194900.0,
"currency": "NGN",
"accountName": "Bello Ramon",
"phoneNumber": "08012345678",
"firstName": "Bello",
"lastName": "Ramon",
"email": "[email protected]",
"iifiNumber": "22222132329",
"fundingDailyLimit": 2000000.0,
"fundingTransactionLimit": 500000.0,
"callbackUrl": "https://yourstore.com/webhooks/paga"
}Update an account
PUT /subsidiary-accounts/{accountIdentifier}
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(referenceNumber + accountIdentifier + iifiNumber + autoSweep.destination + autoSweep.bankPublicId + autoSweep.accountNumber + callbackUrl + hashKey)
Content-Type: application/json
The order differs from createUpdate puts
autoSweep.destinationbeforeautoSweep.bankPublicId. Create putsautoSweep.bankPublicIdfirst. Use the right order for each, or the hash won’t match.
{
"referenceNumber": "REF-UPDATE-001",
"accountName": "St Jones School",
"status": "DISABLED"
}Send only what you want to change. accountNumber and accountReference can’t be changed. Set autoSweep to null to turn a virtual account into a ledger-backed one. Returns the full updated account, as in Retrieve.
Get the balance
GET /subsidiary-accounts/{accountIdentifier}/balance?referenceNumber={referenceNumber}
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(referenceNumber + accountIdentifier + hashKey){
"referenceNumber": "REF-BAL-001",
"statusCode": "0",
"statusMessage": "success",
"accountNumber": "0750529406",
"accountReference": "CUST-000184-WALLET",
"balance": 98000.0,
"currency": "NGN",
"timeStamp": "2026-06-10T22:50:16"
}Get transaction history
GET /subsidiary-accounts/{accountIdentifier}/history?referenceNumber={referenceNumber}&startDateTimeUTC={start}&endDateTimeUTC={end}&pageSize=20&pageNumber=0
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(accountIdentifier + startDateTimeUTC + endDateTimeUTC + hashKey)referenceNumber is required in the query, but it isn’t part of this hash. The date range can be up to 3 months. Pages start at 0, with 20 transactions per page by default.
{
"referenceNumber": "REF-HIST-001",
"statusCode": "0",
"statusMessage": "success",
"itemCount": 3,
"totalItems": 27,
"totalPages": 3,
"currentPage": 0,
"transactions": [
{
"transactionType": "CREDIT",
"amount": 1500.00,
"transactionDate": "2026-06-11T10:22:16.920",
"status": "SUCCESSFUL"
}
]
}Errors and retries
Subsidiary Accounts use the Collect API’s status codes. Every response includes a statusCode and statusMessage. 0 means success. Other codes can mean the request failed, is pending, or was a duplicate, so look each one up in Collect API status codes before you act.
{
"referenceNumber": "REF-CHARGE-001",
"statusCode": "102",
"statusMessage": "Duplicate request. A request with reference number REF-CHARGE-001 has already been processed"
}
Timed out? Don’t reverse anything yetThe money may already have moved. For a charge, top-up or transfer, wait for the callback, or retry with the same
referenceNumber, which returns the original result without moving money twice.For an account you were creating, retrieve it by its
accountReferencebefore trying again. If it exists, use it. Sending the create request again returns a duplicate request response.
Good to know
Add the BVN
iifiNumber is optional, but it lets Paga run fraud checks on payments into the account.
Set a global callback
Set one in the business portal under Developer Tools, so every account is covered. Use an account’s callbackUrl only when it needs its own.
Disable instead of deleting
Set status to DISABLED to stop an account receiving or moving money, and re-enable it later.
Replacing Persistent Payment Identifiers
Subsidiary Accounts are the recommended way to give customers permanent account numbers in new integrations.
Need help? We’re here 24/7
Talk to our support team any time, or ask questions in the developer community.
Updated 3 days ago

