Skip to content

Frequently Asked Questions

Answers to common questions about integrating with the PayOnUs API. If your question isn't covered here, check the Glossary for term definitions or the relevant guide linked in each answer.

Integration & Authentication

How do I get started integrating with PayOnUs?

Sign up on the Merchant Portal, generate your Client ID and Client Secret under Settings → API Credentials, and retrieve your Business ID from the Business section (toggle Test Mode OFF to see it). See API Integration for the full walkthrough.

What's the difference between Sandbox and Production?

They're separate environments with separate base URLs, credentials, webhook settings, and IP whitelist rules:

  • Sandbox: https://core-sandbox.payonus.com
  • Production: https://core.payonus.com

Use the Test Mode toggle on the Merchant Dashboard to switch which environment's settings you're viewing/editing. See API Integration and the Glossary.

How do I authenticate my requests?

Generate a Bearer access token by calling POST /api/v1/access-token with your apiClientId and apiClientSecret, then include it as Authorization: Bearer {access_token} on every request. See API Integration → Authentication.

How long does an access token last, and can I have more than one?

Tokens expire after expires_in seconds (~24 hours). You can only have one valid token at a time — generating a new one immediately invalidates the previous one. Cache your token until shortly before it expires rather than generating a new one per request, and share the cached token across instances if you run a distributed system (e.g. via Redis). See API Integration → Notes.

I'm getting rate limited generating access tokens. Why?

The token endpoint allows at most 3 calls per minute; exceeding that returns 429 Too Many Requests. This is almost always a sign your app isn't caching the token — cache it and only regenerate on expiry or a 401 Unauthorized.

What are businessId and merchantId, and which do I use?

  • Merchant ID identifies your top-level merchant account (used in merchant-scoped endpoints like wallet queries).
  • Business ID identifies a specific business under your merchant account, and is required on almost all payin/payout endpoints.

See the Glossary for where to find each.

What should I use onusReference vs merchantReference for?

merchantReference is your own reference, unique per transaction within your business — reusing one returns 409 Conflict. onusReference is the system-generated reference PayOnUs assigns to every transaction, used for status checks, reconciliation, and support escalations. Always store both. See the Glossary.

Webhooks

How do I receive payment/payout notifications?

Configure a Webhook URL and Webhook Verification Key on the Merchant Dashboard (separately per environment). PayOnUs POSTs a JSON payload — delivered with Content-Type: text/plain, so parse the body as JSON yourself — to your URL for COLLECTION and PAYOUT events. See Webhook Notifications.

How do I verify a webhook actually came from PayOnUs?

Concatenate accountNumber + onusReference + paymentStatus + verificationKey (no delimiters), compute the SHA-256 hex hash, and compare it against the hash header using a constant-time comparison. Only trust the payload after this check passes. See Verifying Webhook Signatures.

My webhook endpoint is slow / doing heavy processing. Is that a problem?

Yes — respond with HTTP 200 within about 5 seconds and defer heavy work (e.g. business logic, emails) to a background job. Also implement idempotency keyed on onusReference, since the same event may be delivered more than once.

I didn't receive a webhook for a transaction — what now?

Don't rely on webhooks alone for anything time-sensitive. Poll Verify Single Payment with the onusReference, or List Payment Requests / Fetch List of Transfer Requests to check the current status directly. Also, do confirm that you have configured both your Webhook Notification URL and your Webhook Verification Key on the Merchant Dashboard.

Payins

Dynamic account vs. fixed account — which should I use?

  • Dynamic accounts (docs) are temporary, created per transaction/customer, and are ideal for one-time payments you want to track individually.
  • Fixed accounts (docs) are permanent, assigned once to a customer or your business, and don't expire — ideal for recurring payments or long-term customer relationships.

Why did my Fixed Account creation request fail with a validation error?

Fixed accounts require more customer detail than dynamic accounts — customer.address.line1, customer.address.city, customer.address.countryCode, customer.bvn, and dob are all required, unlike dynamic accounts where most address fields and BVN are optional. Double-check the Request Body Table.

How does the Mobile Money OTP flow work?

Call Initiate Mobile Money Collection. If the response's otpRequired is true, the customer's network requires an OTP step — call Verify OTP with the onusReference and the OTP the customer received to complete the charge. Some networks (e.g. Orange Côte d'Ivoire) instead need a Pre-OTP initiatingCode supplied upfront on the initiate call.

How do I know which momoNetwork value to use for a customer's country?

Call Fetch List of MoMo Networks — it returns the supported network codes along with their currency and country code.

How do I let a customer pay with a card without building a card form / handling PCI scope?

Use Card Tokenization — you create a consent link, send it to the customer, and they enter their card on a PayOnUs-hosted page. You never touch card data. Once tokenized, charge the card anytime via Charging a Tokenized Card with no further OTP/PIN/3DS step.

No. A link is single-use — it can't be reused once COMPLETED, EXPIRED, or CANCELLED — and its merchantReference is permanently tied to that link for your business, even after it stops being usable. Create a new link with a different merchantReference for a retry. See Link Statuses.

How do I refund a payment?

Only bank transfer payins can be refunded via the API today, using Refund Bank Transfer with the original transaction's onusReference. The refund is asynchronous — it starts PENDING and moves to SUCCESSFUL/FAILED, with a webhook sent on completion.

How do invoice charges affect the total amount?

Each charge is either a CHARGE (adds to the total, e.g. shipping, tax) or a DEDUCTION (subtracts, e.g. a discount). totalAmount = sum(item amounts) + sum(CHARGE amounts) − sum(DEDUCTION amounts), and must end up greater than zero — always send charges[].amount as a positive number regardless of category. See Invoices → Concepts.

Does an invoice automatically re-bill on its frequency?

No. frequency (ONE_TIME, WEEKLY, etc.) is metadata only — PayOnUs does not automatically regenerate or re-charge the invoice. You need to create a new invoice yourself each billing cycle. See Invoices → Concepts.

How do I check on a payment's status after the fact?

Use Verify Single Payment with the onusReference — this works for every payin method (card, dynamic/fixed account, mobile money, EFT). For bulk/filtered lookups, use List Payment Requests.

Payouts

What do I need to do before my first payout works?

Two prerequisites are easy to miss: your server's outbound IP(s) must be whitelisted on the dashboard (Settings → IP Whitelist) for the active environment, and your wallet needs a sufficient balance in the payout currency. See Payouts → Prerequisites.

I'm getting a "wallet not found" error for a specific currency — why?

That currency's wallet likely hasn't been enabled for your business yet. On the dashboard, switch Test Mode to ON, then go to Account → Wallet → (Select Business) and select the currency from the dropdown to enable it. See Testing Your Integration.

Do I always have to call Name Enquiry before a transfer?

Yes — always verify the beneficiary's bank account details with Name Enquiry before calling Initiate Bank Transfer, to avoid sending funds to invalid or mismatched accounts.

How do I pay out to Mobile Money or EFT instead of a bank account?

Both go through the same /api/v1/transfer-requests/bank-transfer endpoint — set transferType to WALLET_TO_MOMO (with momoNetwork) or WALLET_TO_EFT (with eftBank) instead of WALLET_TO_BANK_ACCOUNT. See Initiate Mobile Money Payout and Initiate EFT Payout.

What's a sub-wallet, and how is it different from my main business wallet?

A sub-wallet is an additional wallet under the same business (e.g. for payroll or a specific project), funded from your main wallet, that can itself be used as the payout source by setting fromSubWallet: true and walletId to the sub-wallet's walletId. See Business Sub-Wallets and Paying Out From a Sub-Wallet.

How do I send many payouts at once instead of calling the transfer endpoint repeatedly?

Use Bulk Transfers — upload a CSV of beneficiaries via multipart/form-data to /api/v1/transfer-requests/bulk/bank-transfer with a single reference for the whole batch.

How do I know how much money is still owed to me from collections?

Use Pending Settlements Summary — it returns the total amount and count of collections not yet paid out to your wallet, for a given business and currency, over the trailing 30 days.

Troubleshooting & Errors

What do PayOnUs's HTTP status codes mean?

Standard conventions apply: 2xx success, 4xx a problem with your request (e.g. 400 missing/invalid parameter, 401 bad/missing token, 403 insufficient permission, 404 not found, 409 conflict — usually a duplicate reference, 429 rate limited), 5xx a PayOnUs-side error. See Error Handling for the full table and response shape.

I'm getting 409 Conflict — what does that usually mean?

Almost always a duplicate identifier: a reference/merchantReference you've already used for that business (on a payin, payout, invoice, sub-wallet, or card tokenization link), or — for card tokenization links specifically — a link already exists with that exact reference regardless of its status. Use a new, unused reference.

I'm getting 401 Unauthorized intermittently. Why?

Your access token has likely expired mid-session (tokens last ~24 hours and are invalidated the moment a new one is generated, including by another process sharing your credentials). Cache tokens for a bit less than expires_in, and implement a retry-with-refresh on 401. See API Integration → Notes.

How do I simulate a successful (or failed) payin/payout in Sandbox?

Use the simulation endpoints and test account numbers documented in Testing Your Integration — e.g. POST /api/v1/fund-ngn-account for NGN bank transfer payins, or specific magic account numbers (like 9999991112 for a successful transfer) for NGN payouts. Mobile Money and card testing have their own dedicated test numbers/cards linked from the same guide.

Where can I find a Postman collection to explore the API?

See the Postman Collection linked from API Integration.