Bank transfer (ACH, Wire and Swift)
Collect payments in USD from customers globally.
Give a customer abroad a single-use US bank account. They pay in dollars by ACH or wire, and you receive naira in your Paga balance, converted at the prevailing exchange rate when the payment arrives.
| Environment | Base URL |
|---|---|
| Sandbox | https://qa1-collect.paga.com |
| Live | https://collect.paga.com |
Authenticate with HTTP Basic and a hash header, as in Authentication. Set your callback URL to an HTTPS endpoint that responds with HTTP 200 within 5 seconds.
How it works
Create a payment request
Send the amount you want to collect in naira. Paga returns single-use USD account details for three payment rails, with the dollar amount to request on each.
Show your customer how to pay
Display the instructions for the rail they’ll use, including the exact dollar amount, which already includes that rail’s fee.
Your customer sends dollars
By ACH, US domestic wire or international (SWIFT) wire, from their own bank.
Paga notifies you
A CAPTURED webhook when the payment clears, or FUNDING_FAILED if it can’t be accepted.
Naira settles to your Paga balance
Paga converts the dollars at the prevailing rate when the payment arrives. Verify the webhook, then fulfil the order.
Three ways your customer can pay
ACH
For customers with a US checking account. The lowest fee, and the slowest to clear.
US domestic wire
For customers in the US who want the payment to clear faster.
International wire (SWIFT)
For customers outside the US. They must include the reference Paga - <account number> in the transfer memo.
Each rail has its own fee and minimum amount, returned when you create the request. Show your customer the total for the rail they choose.
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/jsonUse an empty string for payer.email in the hash if you don’t send it. The hash is lowercase hex.
{
"referenceNumber": "INV-2026-0042",
"amount": "80000",
"currency": "NGN",
"payer": {
"name": "Jill Scott",
"phoneNumber": "+19175247723",
"currency": "USD"
},
"payee": {
"name": "Your business name"
},
"expiryDateTimeUTC": "2026-11-10T15:35:00",
"payerCollectionFeeShare": "1.0",
"recipientCollectionFeeShare": "0.0",
"isAllowPartialPayments": "false",
"isAllowOverPayments": "true",
"callBackUrl": "https://yourstore.com/webhooks/paga",
"narration": "Invoice 0042",
"paymentMethods": ["BANK_TRANSFER"]
}All request parameters (17)
| Parameter | Required | Description |
|---|---|---|
referenceNumber | Yes | Your unique reference. Use it to match webhooks and check status |
amount | Yes | The amount you want to collect, in naira |
currency | Yes | Your settlement currency. Must be NGN |
payer.name | Yes | Customer’s full legal name |
payer.phoneNumber | Yes | With country code, for example +19175247723 |
payer.currency | Yes | Must be USD |
payer.email | No | Customer’s email address |
payee.name | Yes | Name shown as the recipient on the USD account |
payerCollectionFeeShare | Yes | Share of fees the customer pays. 1.0 means all of it |
recipientCollectionFeeShare | Yes | Share of fees you absorb. 0.0 means none |
isAllowOverPayments | No | Set to true. Intermediary banks often change wire amounts slightly |
isAllowPartialPayments | No | Set to false to reject underpayments |
expiryDateTimeUTC | No | When the account details stop accepting payments, in UTC |
isSuppressMessages | No | true stops Paga emailing or texting the customer |
callBackUrl | Yes | Where Paga sends webhooks |
narration | No | A short description of the payment |
paymentMethods | Yes | Must be ["BANK_TRANSFER"] |
The response
You get one entry per rail in paymentMethods, each with the account details and the dollar amount for that rail.
Response fields you’ll use most (7)
| Field | What it’s for |
|---|---|
totalPaymentAmount | The exact dollar amount to show your customer, fee included |
transferAccountNumber | The single-use USD account number |
transferFinancialInstitutionRoutingNumber1 | ACH routing number, or SWIFT code for international wires |
transferAccountInstructions.md | Ready-made instructions for international wires, including the memo reference |
minimumSourceAmount | The smallest payment that rail accepts |
foreignExchangeRate | The indicative naira-per-dollar rate used to calculate the dollar amount. The final rate is set when the payment arrives |
rateExpiryMillis | When the quoted dollar amount stops being valid. Create a new request after this |
2. Show your customer how to pay
Show the details for the rail your customer will use. These are from the example response:
Amount to send: $51.50 USD
| Account type | Checking |
| Bank name | Regent Bank |
| Account number | 104723884136673 |
| Routing number (ACH) | 359867415 |
| Recipient | Your business name |
Amount to send: $63.65 USD
| Account type | Checking |
| Bank name | Regent Bank |
| Account number | 104723884136673 |
| Routing number (wire) | 359867415 |
| Recipient | Your business name |
Amount to send: $78.83 USD
| Receiving bank | The Bankers Bank |
| SWIFT / BIC | BBOK US 44 |
| Routing number | 021000018 |
| Bank address | 9020 N. May Avenue, Ste 200, Oklahoma City, OK 73120, USA |
| Beneficiary bank | Regent Bank, account 10135 |
| Memo (required) | Paga - 104723884136673 |
The memo is requiredWithout
Paga - <account number>in the transfer memo, the intermediary bank can’t route the payment.
The amounts differ because each rail’s fee is added on top. Show the total for the rail your customer picks, so the payment isn’t short.
3. Handle webhooks
Paga sends one of two events to your callback URL. Respond with HTTP 200 within 5 seconds, or Paga retries.
The payment cleared and naira is on its way to your Paga balance. Verify, then fulfil.
Paga received money but couldn’t accept it, for example an amount mismatch. Read errorCode and fundsReturnStatus.
Webhook fields to read first (8)
| Field | Meaning |
|---|---|
event | CAPTURED or FUNDING_FAILED |
referenceNumber | Your reference, to match the webhook to your order |
paymentId | Paga’s ID for the payment. Store it with your reference |
receivedAmounts | Dollars that arrived from the customer |
serviceDetails.destinationAmount | Naira credited to your Paga balance |
serviceDetails.foreignExchangeRate | The prevailing rate applied when the payment arrived |
errorCode, errorMessage | Why a payment failed. Only on FUNDING_FAILED |
fundings[].fundsReturnStatus | Whether the money went back to the customer. NOT_RETURNED means you need to contact support |
{
"event": "CAPTURED",
"referenceNumber": "INV-2026-0042",
"paymentId": "01a130c8-fec1-78e9-9bb1-a90e5797e9a0",
"paymentStatus": "CAPTURED",
"receivedAmounts": { "currency": "USD", "amounts": { "BASE": 515.06 } },
"serviceDetails": {
"sourceCurrency": "USD",
"destinationCurrency": "NGN",
"foreignExchangeRate": 1670.5475,
"destinationAmount": 800000.0
}
}The full payload also includes fees, fundings and payment method details.
4. Check payment status
Use the status endpoint to confirm a payment if a webhook hasn’t arrived. For USD collection, include the payment currency.
POST /status
Authorization: Basic base64(publicKey:secretKey)
hash: sha512(referenceNumber + paymentCurrency + hashKey)
Content-Type: application/json{
"referenceNumber": "INV-2026-0042",
"paymentCurrency": "USD"
}Request parameters (2)
| Parameter | Required | Description |
|---|---|---|
referenceNumber | Yes | The referenceNumber you used when you created the payment request. Hash value 1 |
paymentCurrency | Yes | USD. Hash value 2 |
{
"data": {
"statusCode": "1",
"statusMessage": "pending",
"requestAmount": 400.0,
"totalPaymentAmount": 400.0
},
"statusCode": "0",
"statusMessage": "success"
}Response fields (6)
| Field | Meaning |
|---|---|
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.statusCode | The payment’s own status. 0 means paid, 1 means still pending. 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 to be paid, fee included |
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.
Wires can take a day or more to arrive, so don’t poll on a timer. Wait for the webhook, and only check status if it hasn’t arrived when you’d expect it.
Good to know
Allow overpayments
Set isAllowOverPayments to true. Intermediary banks deduct varying fees from wires, and strict amounts cause rejections.
Store both IDs
Index payments by referenceNumber and paymentId. If a webhook arrives twice, respond 200 without fulfilling again.
Expect the naira amount to vary
Dollars convert at the prevailing rate when they arrive, so the naira you receive can differ from your request. Read destinationAmount in the webhook.
Failed wires aren’t returned automatically
When fundsReturnStatus is NOT_RETURNED, contact support with the paymentId and referenceNumber.
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

