Merchant Developer Portal

Integration guide for the Airpero multi-tenant fintech ecosystem. Build scaleable payment applications on top of the 9PSB infrastructure.

The Airpero SaaS API implements a robust, secure, and multi-tenant resilient architecture. Whether you are using our Dashboard or integrating via API, we provide the tools to manage customers and track financial flows with ease.

Authentication

We use a dual-auth system depending on your integration type. Never share your private keys.

Dashboard (JWT)

Used for UI-based management. Authenticate once and use the Bearer token.

Authorization: Bearer 

Server-to-Server (Keys)

Used for backend automation. Required for customer creation.

x-public-key: pk_...
x-private-key: sk_...
POST /merchants/register

Self-onboard your business to the Airpero ecosystem. Returns your unique API keys and 9PSB funding account.

Headers: None (Public)
Body: businessName, email, password
{
  "businessName": "My Fintech",
  "email": "dev@myfintech.com",
  "password": "secure_password"
}

Success Response:

{
  "success": true,
  "message": "Merchant registered successfully",
  "data": {
    "businessName": "My Fintech",
    "email": "dev@myfintech.com",
    "publicKey": "pk_live_...",
    "privateKey": "sk_live_...",
    "merchantAccountNumber": "99XXXXXXXX",
    "bankName": "9PSB"
  }
}
POST /merchants/login

Authenticate to your merchant dashboard. Returns a JWT Bearer token.

Headers: None (Public)
Body: email, password
{
  "email": "dev@myfintech.com",
  "password": "secure_password"
}

Success Response:

{
  "success": true,
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "merchant": {
    "id": "uuid",
    "businessName": "My Fintech",
    "email": "dev@myfintech.com"
  }
}

Reporting Dashboards

Real-time financial metrics for your business overview.

GET /merchants/stats JWT Required

Fetches cumulative inflow, outflow, and total funds held by your customers.

Headers: Authorization: Bearer <JWT>
{
  "success": true,
  "data": {
    "merchantBalance": "50000.00",
    "totalCustomerFunds": 2500.00,
    "totalInflow": 12000.00,
    "customerCount": 42
  }
}
GET /merchants/transactions JWT Required

Detailed log of all customer financial flows. Support for filtering.

Headers: Authorization: Bearer <JWT>

Query Params: ?period=daily|monthly|yearly&status=success

Success Response:

{
  "success": true,
  "count": 2,
  "data": [
    {
      "transaction_id": "MCH-PAY-abc123",
      "type": "debit",
      "class": "payout",
      "amount": "1000.00",
      "status": "success",
      "createdAt": "2026-04-01T12:00:00.000Z"
    }
  ]
}

Customer Creation

For security, customer creation is restricted to **API Key Auth** only. This prevents unauthorized sessions from minting virtual accounts.

POST /customers API Keys Only

Register a new customer and assign a 9PSB virtual account. The account name will be prefixed with your preview name (or business name by default).

Headers: x-public-key, x-private-key
Body: fullName, email, phoneNumber
{
  "fullName": "Jane Doe",
  "email": "jane@example.com",
  "phoneNumber": "08100000000"
}

Success Response:

{
  "success": true,
  "message": "Customer created successfully",
  "data": {
    "id": "customer-uuid",
    "fullName": "Jane Doe",
    "accountNumber": "99XXXXXXXX",
    "bankName": "9PSB"
  }
}
GET /customers/{id} Auth Required

Retrieve full details for a specific customer, including their balance and virtual account information.

Headers: API Keys OR JWT Bearer

Success Response:

{
  "success": true,
  "data": {
    "id": "customer-uuid",
    "merchantId": "merchant-uuid",
    "fullName": "Jane Doe",
    "email": "jane@example.com",
    "phoneNumber": "08100000000",
    "accountNumber": "99XXXXXXXX",
    "balance": "5000.00",
    "createdAt": "2026-04-01T12:00:00.000Z"
  }
}
POST /customers/{id}/payout Private Key Required

Send money out of a specific customer's balance. Requires Private Key auth.

Headers: x-private-key
Body: amount, accountNumber, bankCode, accountName
{
  "amount": 2000,
  "accountNumber": "0123456789",
  "bankCode": "90052",
  "accountName": "John Doe"
}

This call returns exactly two possible responses — no others. A payout is never returned as "failed" immediately. If it can't be confirmed as successful right away, it comes back as PENDING instead, with the customer's balance staying debited while confirmation completes. You'll receive a PAYOUT_SUCCESS or PAYOUT_FAILED webhook automatically once it's resolved — see the Webhooks section.

Response 1 — Success (200):

{
  "status": "SUCCESS",
  "responseCode": "00",
  "message": "Transaction Successful",
  "data": {
    "success": true,
    "reference": "PAYOUT-abc123",
    "transaction_id": "PAYOUT-abc123",
    "fee": 20.00
  }
}

Response 2 — Pending (202):

{
  "status": "PENDING",
  "message": "Transaction pending",
  "data": {
    "success": true,
    "reference": "PAYOUT-abc123",
    "fee": 20.00,
    "transaction_id": "PAYOUT-abc123"
  }
}

You can also check a transaction's current status anytime via /transactions/requery or your transaction history.

POST /customers/{id}/debit Auth Required

Directly debit a customer's balance (moves funds to your merchant wallet).

Headers: API Keys OR JWT Bearer
Body: amount, reason
{
  "amount": 1500,
  "reason": "Service Charge"
}

Success Response:

{
  "success": true,
  "message": "Successfully debited customer Jane Doe",
  "data": {
    "transaction_id": "MCH-DR-xyz789",
    "amount": 1500.00,
    "fee": 0,
    "status": "success"
  }
}
POST /customers/{id}/credit Auth Required

Directly credit a customer's balance from your merchant wallet.

Headers: API Keys OR JWT Bearer
Body: amount, narration
{
  "amount": 2500,
  "narration": "Customer Reward"
}

Success Response:

{
  "success": true,
  "message": "Successfully credited customer Jane Doe",
  "data": {
    "transaction_id": "MCH-CR-abc123",
    "amount": 2500.00,
    "fee": 0,
    "status": "success"
  }
}

Bank Utilities

Helper tools for bank identification and account validation.

GET /customers/:customerId/report Auth Required

Fetch summarized inflow/outflow report for a customer.

Headers: API Keys OR JWT Bearer
Query Params: period (daily/monthly/yearly), startDate, endDate

Success Response:

{
  "success": true,
  "data": {
    "totalVolume": 5000.00,
    "totalCredit": 3500.00,
    "totalDebit": 1500.00,
    "transactionCount": 12
  }
}
GET /customers/:customerId/transactions Auth Required

Get the full granular list of transactions for a customer.

Headers: API Keys OR JWT Bearer

Success Response:

{
  "success": true,
  "count": 1,
  "data": [
    {
      "transaction_id": "DEP-123",
      "type": "credit",
      "amount": 500.00,
      "status": "success"
    }
  ]
}
GET /banks Auth Required

Get a list of all Nigerian banks and their codes.

Headers: API Keys OR JWT Bearer

Success Response:

{
  "success": true,
  "data": [
    { "name": "Access Bank", "code": "044" },
    { "name": "Guaranty Trust Bank", "code": "058" }
  ]
}
POST /banks/enquiry Auth Required

Verify the account name for a given number and bank.

Headers: API Keys OR JWT Bearer
Body: accountNumber, bankCode
{
  "accountNumber": "0123456789",
  "bankCode": "058"
}

Success Response:

{
  "success": true,
  "data": {
    "accountName": "JOHN DOE",
    "accountNumber": "0123456789",
    "bankCode": "058"
  }
}
POST /transactions/requery Auth Required

Verify the status of a specific transaction ID. Optional `accountNo` improves accuracy.

Headers: API Keys OR JWT Bearer
Body: transactionId, accountNo (optional)
{ 
  "transactionId": "PAYOUT-12345",
  "accountNo": "0123456789"
}

Success Response:

{
  "success": true,
  "data": {
    "status": "SUCCESS",
    "amount": 2000,
    "transactionId": "PAYOUT-12345",
    "updatedAt": "2026-04-01T15:00:00Z"
  }
}

Webhooks & Hooks

Airpero delivers instant POST notifications. We recommend logging all inbound payloads for audit purposes.

PATCH /merchants/webhook JWT Required

Update your destination URL for notifications.

Headers: Authorization: Bearer <JWT>
Body: webhookUrl
{ "webhookUrl": "https://api.yourdomain.com/hooks" }

Success Response:

{
  "success": true,
  "message": "Webhook URL updated successfully",
  "data": {
    "businessName": "My Fintech",
    "webhookUrl": "https://api.yourdomain.com/hooks"
  }
}
PATCH /merchants/profile JWT Required

Update your business name or current 'preview' prefix for virtual accounts.

Headers: Authorization: Bearer <JWT>
Body: preview, businessName
{ "preview": "AIRPERO", "businessName": "Airpero Global" }

Success Response:

{
  "success": true,
  "message": "Profile updated successfully",
  "data": {
    "preview": "AIRPERO",
    "businessName": "Airpero Global"
  }
}

Detailed Webhook Payload (PAYIN_SUCCESS):

Sent to your callback URL when a customer successfully deposits funds via their virtual account.

{
  "event": "PAYIN_SUCCESS",
  "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "timestamp": "2026-04-06T10:45:10.000Z",
  "data": {
    "transactionId": "100004260402085201156174739776",
    "customerId": "88776655-4433-2211-0099-aabbccddeeff",
    "customerName": "DESTINY MOMODU",
    "senderName": "DANIEL OSAGIE ABIEVBODE",
    "senderAccountNumber": "8032184432",
    "senderBankName": "Palmpay",
    "senderBankCode": "100033",
    "amount": 10000.00,
    "fee": 50.00,
    "total_amount": 10000.00,
    "currency": "NGN",
    "reference": "CUST-VA-17be8eaf",
    "transactionDate": "2026-04-02T17:09:05"
  }
}

Detailed Webhook Payload (WALLET_FUNDED):

Sent to your callback URL when you fund your own merchant wallet directly.

{
  "event": "WALLET_FUNDED",
  "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "timestamp": "2026-04-06T10:45:10.000Z",
  "data": {
    "transactionId": "100004260402085201156174739776",
    "amount": 50000.00,
    "currency": "NGN",
    "reference": "MCH-VA-ref123",
    "senderName": "DANIEL OSAGIE ABIEVBODE",
    "senderAccountNumber": "8032184432",
    "senderBankName": "Palmpay",
    "transactionDate": "2026-04-02T17:09:05"
  }
}

Detailed Webhook Payload (PAYOUT_SUCCESS):

Sent when a payout that came back PENDING from /merchants/payout or /customers/{id}/payout is confirmed successful. A payout that returns an immediate 200 SUCCESS does not trigger this webhook — it's only sent for the delayed-confirmation case. customerId is null for a merchant's own payout.

{
  "event": "PAYOUT_SUCCESS",
  "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "timestamp": "2026-04-06T10:45:10.000Z",
  "data": {
    "transaction_id": "PAYOUT-abc123",
    "reference": "PAYOUT-abc123",
    "customerId": "88776655-4433-2211-0099-aabbccddeeff",
    "amount": 5000.00,
    "fee": 20.00,
    "currency": "NGN",
    "status": "success"
  }
}

Detailed Webhook Payload (PAYOUT_FAILED):

Sent when a payout that came back PENDING is confirmed to have truly failed. By the time this fires, the debited balance has already been refunded — refunded is always true. customerId is null for a merchant's own payout.

{
  "event": "PAYOUT_FAILED",
  "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "timestamp": "2026-04-06T10:45:10.000Z",
  "data": {
    "transaction_id": "PAYOUT-abc123",
    "reference": "PAYOUT-abc123",
    "customerId": "88776655-4433-2211-0099-aabbccddeeff",
    "amount": 5000.00,
    "fee": 20.00,
    "currency": "NGN",
    "status": "failed",
    "reason": "Confirmed failed by the provider after repeated verification.",
    "refunded": true
  }
}

Payout Operations

Initiate transfers out of your merchant wallet to any destination bank.

💡 Stamp Duty: Transfers above ₦10,000 attract a mandatory ₦50 stamp duty fee, as per regulatory requirements.
POST /merchants/payout Auth Required

Real-time transfer via 9PSB infrastructure.

Headers: API Keys OR JWT Bearer
Body: amount, accountNumber, bankCode, accountName, narration
{
  "amount": 1000,
  "accountNumber": "0123456789",
  "bankCode": "90052",
  "accountName": "John Doe",
  "narration": "Monthly Settlement"
}

This call returns exactly two possible responses — no others. A payout is never returned as "failed" immediately. If it can't be confirmed as successful right away, it comes back as PENDING instead, with your wallet staying debited while confirmation completes. You'll receive a PAYOUT_SUCCESS or PAYOUT_FAILED webhook automatically once it's resolved — see the Webhooks section.

Response 1 — Success (200):

Note: this endpoint's success shape differs from /customers/{id}/payout's — success is a top-level field here, and data is whatever the provider itself returned plus fee, rather than an explicit reference/transaction_id pair.

{
  "success": true,
  "message": "Payout initiated successfully",
  "status": "SUCCESS",
  "responseCode": "00",
  "data": {
    "fee": 20.00
  }
}

Response 2 — Pending (202):

{
  "status": "PENDING",
  "message": "Transaction pending",
  "data": {
    "success": true,
    "reference": "MCH-PAY-xyz789",
    "status": "SUCCESS",
    "fee": "20.00",
    "transaction_id": "MCH-PAY-xyz789"
  }
}

You can also check a transaction's current status anytime via /transactions/requery or your transaction history.

© 2026 Airpero SaaS. Branded documentation for authorized merchants.