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.

Subsidiary Accounts

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.

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

Holds funds

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.

Settles to Paga

Virtual account (business settlement)

Every payment goes straight to your Paga business account. The account keeps no balance.

autoSweep.destination: VIRTUAL_ACCOUNT

Settles to your bank

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)
TermMeaning
accountReferenceIdentifies the account. Your own unique ID, 11 to 30 characters. You set it at creation and can’t change it
referenceNumberIdentifies the request. Use a new one for every call
accountNumberThe 10-digit NUBAN Paga generates for the account. Give this to your customer
accountIdentifierThe account an operation applies to. Pass the account’s accountReference
iifiNumberYour 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/json

Note 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"
}
All request parameters (16)
ParameterRequiredDescription
referenceNumberYesUnique reference for this request. Hash value 1
accountReferenceYesYour unique ID for the account, 11 to 30 characters. Hash value 2
accountNameYesName shown on the account
firstNameYesCustomer’s first name
lastNameYesCustomer’s last name
phoneNumberOne ofCustomer’s phone number. Send this, email, or both
emailOne ofCustomer’s email. Send this, phoneNumber, or both
iifiNumberNoCustomer’s BVN. Recommended for fraud checks. Hash value 3
callbackUrlNoWebhook URL for this account only. Overrides your global callback. Hash value 7
fundingDailyLimitNoMost the account can receive in a day. Transfers above it are declined by the sender’s bank
fundingTransactionLimitNoMost the account can receive in one payment
statusNoACTIVE (default) or DISABLED. Disabled accounts can’t receive or move money
autoSweepNoLeave out for a ledger-backed account that holds funds
autoSweep.destinationNoVIRTUAL_ACCOUNT to settle to your Paga business account, or BANK to settle to a bank. Hash value 5
autoSweep.bankPublicIdIf BANKThe bank’s ID, from Get Banks (GET /banks). Hash value 4
autoSweep.accountNumberIf BANKThe 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)
FieldMeaning
referenceNumberYour request reference
statusCode0 means the account was created
statusMessageReadable status
accountNumberThe account number to give your customer
accountReferenceYour account ID
statusACTIVE or DISABLED
balanceAlways 0.0 when created
currencyNGN

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)
FieldMeaning
statusCode0 means the payment was received
transactionReferencePaga’s reference for the credit. Can be null
fundingTransactionReferencePaga’s reference for the incoming transfer
fundingPaymentReferenceThe sender’s original payment reference
accountNumberThe subsidiary account that was paid
accountNameName on the account
financialIdentificationNumberCustomer’s BVN, if you set it
amountAmount received
clearingFeeAmountFee deducted. Can be null
payerDetailsWho paid: method, name, bank and account number
narrationThe transfer’s narration
transactionDateWhen 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 only

Charge, 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
}
Charge, top-up and transfer parameters
ParameterRequiredDescription
referenceNumberYesUnique reference. Reuse it only to retry the same operation
amountYesAmount to move
currencyYesNGN
narrationNoDescription. Use an empty string in the hash if you leave it out
sourceAccountIdentifierTransfer onlyThe accountReference of the account to take money from
destinationAccountIdentifierTransfer onlyThe 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"
}

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)
FieldMeaning
eventSUBSIDIARY_ACCOUNT_CHARGE_COMPLETE, SUBSIDIARY_ACCOUNT_TOP_UP_COMPLETE or SUBSIDIARY_ACCOUNT_TRANSFER_COMPLETE
notificationIdUnique ID for this notification. Retries use the same one, so de-duplicate on it
statusCode0 means the operation completed
statusMessageSUCCESS
externalReferenceNumberThe referenceNumber you sent with the request
transactionTypeDEBIT for charges and transfers (sent for the source account only), CREDIT for top-ups
transactionDateWhen the operation happened, in UTC
accountReferenceThe account it applied to
totalDebitAmountamount plus fee
feeProcessing fee. 0.0 if none
amountNet amount, as a formatted string. Use it exactly as received in the hash
accountNumberThe account’s NUBAN
hashSignature to check before you trust the notification
timestampWhen 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 create

Update puts autoSweep.destination before autoSweep.bankPublicId. Create puts autoSweep.bankPublicId first. 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 yet

The 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 accountReference before 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.