API v3

API documentation

The UltraPay Pay API follows Flutterwave's v3 conventions, so existing integrations port over with a new base URL and keys. Base URL: https://api.ultrapay.ng/v3

Authentication

Send your secret key as a bearer token. Test keys start UPSECK_TEST-, live keys UPSECK_LIVE-. The key decides the mode. Never put a secret key in a browser or app; use your public key (UPPUBK_…) with inline checkout instead.

Authorization: Bearer UPSECK_TEST-xxxxxxxxxxxxxxxx-X

Create a payment (hosted checkout)

POST https://api.ultrapay.ng/v3/payments
{
  "tx_ref": "order-1001",
  "amount": 5000,
  "currency": "NGN",
  "redirect_url": "https://yourshop.com/payment/callback",
  "customer": { "email": "ada@example.com", "name": "Ada Obi", "phonenumber": "08030000000" },
  "customizations": { "title": "Your Shop", "description": "Order 1001", "logo": "https://yourshop.com/logo.png" },
  "meta": { "order_id": 1001 }
}

200 OK
{ "status": "success", "message": "Hosted Link", "data": { "link": "https://api.ultrapay.ng/pay/…" } }

Redirect the customer to data.link. When they finish, we send them to your redirect_url with ?status=successful|failed|cancelled&tx_ref=…&transaction_id=…. Always verify the transaction before giving value: never trust the redirect alone.

Verify a transaction

GET https://api.ultrapay.ng/v3/transactions/{transaction_id}/verify
GET https://api.ultrapay.ng/v3/transactions/verify_by_reference?tx_ref=order-1001

{ "status": "success", "data": { "id": 100023, "tx_ref": "order-1001", "flw_ref": "UPAY-…",
  "amount": 5000, "currency": "NGN", "charged_amount": 5000, "app_fee": 70, "amount_settled": 4930,
  "status": "successful", "payment_type": "ultrapay_account", "customer": { … }, "meta": { … } } }

Give value only when data.status is successful, data.amount matches and data.currency is NGN.

List transactions and balances

GET https://api.ultrapay.ng/v3/transactions?page=1&status=successful&from=2026-10-01&to=2026-10-31
GET https://api.ultrapay.ng/v3/balances

Webhooks

Set a webhook URL and a secret hash in the dashboard (per mode). We POST when a payment completes, with your secret hash in the verif-hash header. Reply 200 quickly; we retry failures for about 15 hours.

POST https://yourapp.com/webhooks/ultrapay
verif-hash: <your secret hash>

{ "event": "charge.completed",
  "data": { "id": 100023, "tx_ref": "order-1001", "flw_ref": "UPAY-…", "amount": 5000, "currency": "NGN",
            "status": "successful", "payment_type": "ultrapay_account", "customer": { … }, "meta": { … } } }

Inline checkout (no backend)

<script src="https://api.ultrapay.ng/inline.js"></script>
<script>
  UltraPayCheckout({
    public_key: "UPPUBK_TEST-xxxx-X",
    tx_ref: "order-" + Date.now(),
    amount: 5000,
    customer: { email: "ada@example.com", name: "Ada Obi" },
    redirect_url: "https://yourshop.com/payment/callback"
  });
</script>

Open accounts

Open real UltraPay MFB accounts. A customer account is opened in your customer's own name: their BVN or NIN is checked with the registry, and the name and date of birth you send must match it. It belongs to your customer, and this API can never debit it. A sub account is an extra account under your own business (for a branch, an outlet or a collection), held by the same customer as your settlement account. Live use needs UltraPay to enable it for your business.

POST https://api.ultrapay.ng/v3/accounts
{
  "type": "customer",
  "reference": "cust-001",
  "first_name": "Amina", "last_name": "Yusuf",
  "dob": "1994-03-14", "gender": "female",
  "phone_number": "08031234567", "email": "amina@example.com",
  "address": "12 Ahmadu Bello Way, Kano",
  "bvn": "22200000000"            // or "nin": "…", exactly one
}

POST https://api.ultrapay.ng/v3/accounts
{ "type": "sub", "reference": "branch-kano", "label": "Kano branch" }

→ 201
{ "status": "success", "message": "Account created",
  "data": { "id": "…", "type": "customer", "reference": "cust-001",
            "account_number": "5090903001", "account_name": "AMINA YUSUF",
            "bank_name": "ULTRAPAY MFB", "bank_code": "090253", "status": "active", … } }

References are unique per business and mode. 409: the reference is taken, or the person already holds an UltraPay account. 422: no record for that BVN/NIN, or the name or date of birth does not match it.

Account balances

GET https://api.ultrapay.ng/v3/accounts?page=1&type=customer        // the accounts you opened
GET https://api.ultrapay.ng/v3/accounts/{account_number}             // one of them, with its live balance
→ { "data": { "account_number": "5090903001", …,
              "balance": { "available_balance": 25000, "ledger_balance": 25000 } } }

GET https://api.ultrapay.ng/v3/balances                              // your settlement account
→ { "data": [ { "currency": "NGN", "available_balance": 1250400.5, "ledger_balance": 1250400.5,
                "collected": 980000, "fees": 13720, "net": 966280, … } ] }

You can read only accounts you opened. balance is null when the bank cannot be reached.

Banks and name check

GET https://api.ultrapay.ng/v3/banks
→ { "data": [ { "code": "000013", "name": "GTBANK PLC" }, { "code": "090253", "name": "ULTRAPAY MFB" }, … ] }

POST https://api.ultrapay.ng/v3/accounts/resolve
{ "account_number": "0123456789", "account_bank": "000013" }
→ { "data": { "account_number": "0123456789", "account_name": "ADA OKAFOR" } }

Payouts

Send money from your settlement account to any Nigerian bank account, or to another UltraPay account (account_bank 090253 or ultrapay). Payouts only ever debit your own settlement account, within the per-payout and daily limits set for your business. The recipient's name is confirmed with their bank first.

POST https://api.ultrapay.ng/v3/transfers
{
  "reference": "payout-2026-10-10-001",
  "amount": 25000,
  "account_bank": "000013",
  "account_number": "0123456789",
  "narration": "October commission"
}
→ { "status": "success", "message": "Transfer successful",
    "data": { "id": 500001, "status": "SUCCESSFUL", "amount": 25000, "fee": 0,
              "full_name": "ADA OKAFOR", "reference": "payout-2026-10-10-001", … } }

GET https://api.ultrapay.ng/v3/transfers?page=1&status=successful
GET https://api.ultrapay.ng/v3/transfers/{id}
  • status is SUCCESSFUL, FAILED (with the reason in complete_message; no money moved) or PENDING.
  • PENDING means the bank has not answered yet. The money may have moved, so do not send it again: check GET /transfers/{id} or wait for the webhook.
  • A reference can be used once. Sending the same one again returns 409 and pays nothing, so retry with the same reference.
  • Each final payout sends a transfer.completed webhook with the same verif-hash header as payments.

Test mode

  • UltraPay account 0000000000: payment succeeds.
  • UltraPay account 0000000001: payment fails with insufficient funds.
  • The confirmation code is always 123456. No SMS is sent and no money moves.
  • Accounts opened in test mode get numbers starting 99 and a zero balance; no BVN or NIN is checked.
  • Payouts to 0000000000 succeed and to 0000000001 fail with insufficient funds, at any account_bank; other numbers fail as not found.

Going live

Add your UltraPay MFB settlement account in Settings and request live access. After approval you get live keys. Live payments are debited from the customer's UltraPay account (confirmed with a code sent to the phone registered on that account) and credited instantly to your settlement account. Fee: 1.4% per transaction, capped at ₦2,000.

Errors

{ "status": "error", "message": "Invalid authorization key", "data": null }
401 bad or revoked key · 403 suspended or not live-enabled · 400 invalid request · 404 not found