Preloader

Afripay Merchant API

Use this API to manage customers, attach documents, open currency accounts, issue cards, move money, and receive webhooks when things change.

Base URL:
https://sandbox-business.goafripay.com/api/merchant

Authentication

Afripay APIs require authentication using client credentials generated from Merchant Dashboard → Settings → API Credentials (open API credentials).

Option 1: Custom Headers (Recommended)

HTTP Headers
X-Client-Id: mch_live_YOUR_CLIENT_ID
X-Client-Secret: sk_live_YOUR_CLIENT_SECRET
Accept: application/json
Content-Type: application/json

Option 2: Bearer Token

HTTP Headers
Authorization: Bearer sk_live_YOUR_CLIENT_SECRET
Accept: application/json
Content-Type: application/json

Complete End-to-End Architecture

How your app starts a checkout, your customer pays, and Afripay notifies you when the payment succeeds.

Sequence Diagram
1. Third-Party App         2. Afripay Gateway             3. Customer
     │                             │                              │
     ├─ POST /checkout/initialize ─>│                              │
     │  (amount, name, email, ref) │                              │
     │<── Returns checkout_url ────┤                              │
     │                             │                              │
     │── Redirects customer ───────┼─> Opens checkout page ──────>│ Customer sees pre-filled
     │                             │   (/link/{token})            │ name, email & locked amount.
     │                             │                              │ Gets bank transfer details.
     │                             │                              │
     │                             │<── Customer pays transfer ───┤
     │                             │                              │
     │                             │<── Payment confirmed ────────┤ Customer payment succeeds
     │                             │                              │
     │                             ├── Credit your wallet         │
     │<── Afripay Webhook ─────────┤ (charge.success)             │
     │    (Fulfill the order)      │                              │
     │                             │                              │
     │<── Customer Redirect ───────┼──────────────────────────────┤ Browser redirects back to
     │    (to callback_url)        │                              │ merchant's callback_url

1. Where Merchants Configure Their Webhook

Merchants can configure their webhook URL and view their secret directly in the dashboard:

Dashboard Menu: Merchant Dashboard → Settings → Webhooks
URL Route: https://sandbox-business.goafripay.com/merchant/developer/webhooks
(Also accessible via a shortcut button on the API Credentials page at https://sandbox-business.goafripay.com/merchant/developer/api)

What is available on the Webhook page:

FeatureDescription
Webhook URL The merchant pastes their listener endpoint (e.g., https://example.com/api/afripay-webhook). Must be an accessible HTTPS URL.
Status Switch Toggle webhook notifications on or off at any time.
Signing Secret A unique secret key (whsec_...) used to verify signatures with a one-click copy button and a Rotate Secret button.
Send Test Webhook Sends an instant test ping event (webhook.test) to verify that your endpoint responds with HTTP 200.
Delivery History Log Displays every webhook attempt, event name, target URL, HTTP response code, attempt count, timestamp, an Inspect Payload modal, and a Retry button.
Automatic Retries If your server is down or returns an error, Afripay automatically retries delivery (after about 1 minute, then 5 minutes, 15 minutes, 1 hour, 3 hours, and 6 hours).

2. Third-Party Integration API

Step 1: Initialize Checkout

POST /api/v1/checkout/initialize /api/merchant/checkout/initialize

Creates a checkout session for your customer. You can pass the transaction amount, customer details, custom reference, redirect callback URL, and custom metadata.

Request Body Parameters

FieldTypeRequiredDescription
amountnumericYesTransaction amount (e.g. 5000).
currencystringNoCurrency code (default: NGN).
referencestringNoYour unique transaction reference (e.g. ORDER-982134). Auto-generated if omitted.
titlestringNoDescription or payment title displayed to customer.
customer.name / namestringNoCustomer full name. Pre-fills on the payment screen.
customer.email / emailstringYesCustomer email address. Pre-fills on the payment screen.
customer.phone / phonestringNoCustomer phone number.
callback_urlstring (URL)NoURL customer will be redirected to upon successful payment completion.
metadataobjectNoCustom JSON object with key-value pairs stored with this transaction.
Tip: Flat customer fields (email, name, first_name, last_name, phone) are also accepted at the root level for developer convenience.
JSON Request Body
{
  "amount": 5000,
  "currency": "NGN",
  "reference": "ORDER-982134",
  "title": "Payment for Order #982134",
  "customer": {
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "phone": "+2348012345678"
  },
  "callback_url": "https://merchant-store.com/checkout/callback",
  "metadata": {
    "order_id": 982134,
    "user_id": 42
  }
}
JSON Response (201 Created)
{
  "status": true,
  "message": "Checkout link initialized successfully.",
  "data": {
    "checkout_url": "https://sandbox-business.goafripay.com/link/qtujnmgstm",
    "token": "qtujnmgstm",
    "reference": "ORDER-982134",
    "amount": "5000.00",
    "currency": "NGN",
    "title": "Payment for Order #982134",
    "customer": {
      "name": "Jane Doe",
      "first_name": "Jane",
      "last_name": "Doe",
      "email": "jane.doe@example.com",
      "phone": "+2348012345678"
    },
    "callback_url": "https://merchant-store.com/checkout/callback",
    "created_at": "2026-10-06T12:00:00.000000Z"
  }
}

Step 2: Customer Checkout Experience

  1. The merchant redirects their customer to checkout_url (e.g. https://sandbox-business.goafripay.com/link/qtujnmgstm).
  2. When the customer lands on the page:
    • Customer Details: First Name, Last Name, and Email are pre-filled automatically.
    • Amount Locked: The Amount field is locked and readonly to prevent tampering.
  3. The customer clicks Pay and receives dedicated virtual bank transfer account details.
  4. The customer transfers the funds via their bank app. Afripay confirms the incoming transfer.
  5. Automatic Redirect: If callback_url was provided, the checkout page automatically redirects back with query parameters:
    https://merchant-store.com/checkout/callback?status=successful&reference=ORDER-982134&trx=PL-64728192

Step 3: Webhook Notification (Server-to-Server)

When the payment is confirmed, Afripay signs and sends an HTTP POST request to your configured Webhook URL. Full event catalog and payloads are under Webhooks.

HTTP Headers Sent

HeaderDescription
Content-Typeapplication/json
X-Afripay-EventEvent name, e.g. charge.success or charge.failed
X-Afripay-SignatureSignature of the request body, signed with your webhook secret — use this to confirm the webhook came from Afripay
X-Afripay-DeliveryUnique id for this delivery attempt (useful if the same event is retried)

Identifiers in the payload

FieldWhat it means
idAfripay’s unique id for this payment. Store this and use it when you need to look the payment up later.
trx_idShort transaction code you can show on receipts or support tickets (for example PL-64728192).
referenceYour own reference from checkout (for example ORDER-982134).
afripay_referenceAfripay’s payment-link / collection reference for this checkout.
JSON Webhook Payload (charge.success)
{
  "event": "charge.success",
  "data": {
    "id": "a1b2c3d4e5f6789012345678abcdef01",
    "reference": "ORDER-982134",
    "afripay_reference": "PL-QTUJNM-AB12CD",
    "trx_id": "PL-64728192",
    "status": "successful",
    "amount": "5000.00",
    "currency": "NGN",
    "fee": "75.00",
    "amount_settled": "4925.00",
    "payment_method": "bank_transfer",
    "customer": {
      "name": "Jane Doe",
      "email": "jane.doe@example.com",
      "phone": "+2348012345678"
    },
    "payment_link": {
      "token": "qtujnmgstm",
      "title": "Payment for Order #982134"
    },
    "metadata": {
      "order_id": 982134,
      "user_id": 42
    },
    "paid_at": "2026-10-06T12:05:00.000000Z",
    "created_at": "2026-10-06T12:00:00.000000Z"
  }
}

Step 4: Verifying the Webhook on Third-Party Servers

Always verify the X-Afripay-Signature header before processing any webhook payload to ensure the request originated from Afripay.

PHP Verification Example

PHP
<?php
$secret = 'whsec_YOUR_WEBHOOK_SECRET';
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_AFRIPAY_SIGNATURE'] ?? '';

$computedSignature = hash_hmac('sha512', $rawBody, $secret);

if (!hash_equals($computedSignature, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

$payload = json_decode($rawBody, true);

if ($payload['event'] === 'charge.success') {
    $reference = $payload['data']['reference'];
    $amount = $payload['data']['amount'];
    // Update order status to PAID in merchant database
}

http_response_code(200);
echo json_encode(['status' => 'ok']);
?>

Node.js / Express Verification Example

JavaScript (Node.js)
const crypto = require('crypto');

app.post('/api/afripay-webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const secret = process.env.AFRIPAY_WEBHOOK_SECRET;
    const signature = req.headers['x-afripay-signature'];
    const computedSignature = crypto
        .createHmac('sha512', secret)
        .update(req.body)
        .digest('hex');

    if (computedSignature !== signature) {
        return res.status(401).send('Invalid signature');
    }

    const payload = JSON.parse(req.body.toString());
    if (payload.event === 'charge.success') {
        const { reference, amount } = payload.data;
        // Mark order as paid in database
    }

    return res.status(200).json({ received: true });
});

Step 5: Verify Endpoint (Polling Fallback)

If you ever miss a webhook, or want to verify a payment on demand, query the verification endpoint using your reference or Afripay token.

GET /api/v1/checkout/verify/{reference} /api/merchant/checkout/verify/{reference}
cURL
curl -X GET "https://sandbox-business.goafripay.com/api/v1/checkout/verify/ORDER-982134" \
  -H "X-Client-Id: mch_live_YOUR_CLIENT_ID" \
  -H "X-Client-Secret: sk_live_YOUR_CLIENT_SECRET"
JSON Response (200 OK)
{
  "status": true,
  "message": "Payment status retrieved.",
  "data": {
    "reference": "ORDER-982134",
    "afripay_reference": "PL-QTUJNM-AB12CD",
    "status": "successful",
    "amount": "5000.00",
    "currency": "NGN",
    "fee": "75.00",
    "amount_settled": "4925.00",
    "paid_at": "2026-10-06T12:05:00.000000Z"
  }
}

Customers

A customer is the person you open accounts and cards for. Typical flow:

  1. Create the customer (identity and address).
  2. Attach documents when you need foreign accounts.
  3. Open a currency account.
  4. Listen for webhooks when the account is ready or if it fails.

1. List Customers

GET /api/merchant/baas/customers

Query parameters

ParameterTypeDescription
per_pageintegerItems per page (default 20, max 100).
JSON Response (200 OK)
{
  "status": true,
  "message": "Customers fetched successfully",
  "data": [
    {
      "id": "1b04b3ffb7f0458db6357e8b152977b9",
      "first_name": "John",
      "last_name": "Doe",
      "email": "john@example.com",
      "phone": "+2348012345678",
      "date_of_birth": "1990-05-15",
      "bvn": "12345678901",
      "status": "active",
      "address": {
        "line1": "12 Broad Street",
        "city": "Lagos",
        "state": "Lagos",
        "country": "NG"
      },
      "documents": [],
      "currency_accounts_count": 0,
      "cards_count": 0,
      "created_at": "2026-07-10T17:50:55Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "per_page": 20,
    "total": 1
  }
}

2. Create Customer

POST /api/merchant/baas/customers

If a customer with the same email or BVN already exists for your merchant, we return that customer with HTTP 200 instead of creating a duplicate.

Required fields

FieldNotes
first_nameCustomer first name.
emailUnique per merchant.
phoneInclude country code when possible.
date_of_birthMust be 18 or older. Format YYYY-MM-DD.
bvnExactly 11 digits.
address.line1, city, state, countrycountry is a 2-letter code (for example NG).

Optional fields

  • last_name
  • secondary_id_type / secondary_id_number (nin, drivers_license, passport, voters_card)
  • occupation, source_of_funds, and related profile fields — optional at create; required before USD or EUR (see Occupation & Source of Funds)
  • documents — see Documents
Request Body
{
  "first_name": "John",
  "last_name": "Doe",
  "email": "john@example.com",
  "phone": "+2348012345678",
  "date_of_birth": "1990-05-15",
  "bvn": "12345678901",
  "address": {
    "line1": "12 Broad Street",
    "city": "Lagos",
    "state": "Lagos",
    "country": "NG"
  },
  "occupation": "software_developer",
  "source_of_funds": "salary",
  "documents": [
    {
      "type": "passport",
      "url": "https://files.example.com/passport.pdf",
      "expiry_date": "2030-01-01"
    },
    {
      "type": "selfie",
      "url": "https://files.example.com/selfie.jpg"
    },
    {
      "type": "bank_statement",
      "url": "https://files.example.com/statement.pdf"
    }
  ]
}

Documents

Pass public file URLs Afripay can download. You can send documents on create or later on update.

Document object

FieldRequiredNotes
typeYesOne type per customer. See list below.
urlYesMust be reachable over HTTPS.
expiry_dateNo*Required for passport when opening USD or EUR.

Supported types

  • passport — preferred for foreign accounts
  • selfie — required for foreign accounts
  • bank_statement or proof_of_address — proof of address
  • nin, drivers_license, voters_card, id_photo

For USD and EUR, use passport, selfie, and a proof of address. Other currencies may not need file uploads.

3. Get Customer

GET /api/merchant/baas/customers/{customerUuid}

Returns the customer profile, documents, currency accounts, and cards.

JSON Response (200 OK)
{
  "status": true,
  "message": "Customer fetched successfully",
  "data": {
    "id": "1b04b3ffb7f0458db6357e8b152977b9",
    "first_name": "John",
    "last_name": "Doe",
    "email": "john@example.com",
    "phone": "+2348012345678",
    "date_of_birth": "1990-05-15",
    "bvn": "12345678901",
    "nin": null,
    "secondary_id_type": null,
    "secondary_id_number": null,
    "gender": "male",
    "occupation": "software_developer",
    "source_of_funds": "salary",
    "employment_status": "employed",
    "primary_purpose": "purchase_goods_and_services",
    "monthly_volume": "0_4999",
    "status": "active",
    "address": {
      "line1": "12 Broad Street",
      "line2": null,
      "city": "Lagos",
      "state": "Lagos",
      "country": "NG",
      "postal_code": null
    },
    "documents": [
      {
        "id": "c1d2e3f4a5b6789012345678abcdef01",
        "type": "passport",
        "url": "https://files.example.com/passport.pdf",
        "expiry_date": "2030-01-01",
        "verification_status": "verified",
        "created_at": "2026-07-10T17:51:00Z"
      },
      {
        "id": "d2e3f4a5b6789012345678abcdef012",
        "type": "selfie",
        "url": "https://files.example.com/selfie.jpg",
        "expiry_date": null,
        "verification_status": "verified",
        "created_at": "2026-07-10T17:51:00Z"
      },
      {
        "id": "e3f4a5b6789012345678abcdef01234",
        "type": "bank_statement",
        "url": "https://files.example.com/statement.pdf",
        "expiry_date": null,
        "verification_status": "verified",
        "created_at": "2026-07-10T17:51:00Z"
      }
    ],
    "currency_accounts_count": 1,
    "cards_count": 0,
    "created_at": "2026-07-10T17:50:55Z",
    "currency_accounts": [
      {
        "id": "e2c83407880211f183520e74f6457b17",
        "currency_code": "NGN",
        "status": "active",
        "account_number": "3504939056",
        "account_name": "John Doe",
        "bank_name": "Partner Bank",
        "bank_code": "999",
        "routing_number": null,
        "bank_address": null,
        "label": "Acme/John Doe - NGN",
        "created_at": "2026-07-25T09:40:04Z"
      }
    ],
    "cards": []
  }
}

4. Update Customer

PATCH /api/merchant/baas/customers/{customerUuid}

Send only the fields you want to change. To attach or replace documents, send a documents array. Each item needs type and a public url. The same type updates the existing file.

Request Body — attach documents
{
  "occupation": "software_developer",
  "source_of_funds": "salary",
  "documents": [
    {
      "type": "passport",
      "url": "https://files.example.com/passport.pdf",
      "expiry_date": "2030-01-01"
    },
    {
      "type": "selfie",
      "url": "https://files.example.com/selfie.jpg"
    },
    {
      "type": "bank_statement",
      "url": "https://files.example.com/statement.pdf"
    }
  ]
}
JSON Response (202 Accepted)
{
  "status": true,
  "message": "Customer updated successfully",
  "data": {
    "id": "1b04b3ffb7f0458db6357e8b152977b9",
    "first_name": "John",
    "last_name": "Doe",
    "email": "john@example.com",
    "phone": "+2348012345678",
    "date_of_birth": "1990-05-15",
    "bvn": "12345678901",
    "occupation": "software_developer",
    "source_of_funds": "salary",
    "status": "active",
    "address": {
      "line1": "12 Broad Street",
      "city": "Lagos",
      "state": "Lagos",
      "country": "NG"
    },
    "documents": [
      {
        "id": "c1d2e3f4a5b6789012345678abcdef01",
        "type": "passport",
        "url": "https://files.example.com/passport.pdf",
        "expiry_date": "2030-01-01",
        "verification_status": "verified",
        "created_at": "2026-07-10T17:51:00Z"
      },
      {
        "id": "d2e3f4a5b6789012345678abcdef012",
        "type": "selfie",
        "url": "https://files.example.com/selfie.jpg",
        "expiry_date": null,
        "verification_status": "verified",
        "created_at": "2026-07-10T17:51:00Z"
      },
      {
        "id": "e3f4a5b6789012345678abcdef01234",
        "type": "bank_statement",
        "url": "https://files.example.com/statement.pdf",
        "expiry_date": null,
        "verification_status": "verified",
        "created_at": "2026-07-10T17:51:00Z"
      }
    ],
    "currency_accounts_count": 0,
    "cards_count": 0,
    "created_at": "2026-07-10T17:50:55Z",
    "currency_accounts": [],
    "cards": []
  }
}

Cards

6. List Cards

GET /api/merchant/baas/customers/{customer}/cards

Query Params: per_page, currency, label.

7. Create Card

POST /api/merchant/baas/customers/{customerUuid}/cards
Request Body
{
  "currency": "USD",
  "label": "Primary Card",
  "extra": {}
}

8. Get Card

GET /api/merchant/baas/customers/{customerUuid}/cards/{cardId}

9. Fund Card

POST /api/merchant/baas/customers/{customerUuid}/cards/{cardId}/fund
Request Body
{
  "amount": 10.00,
  "extra": {
    "note": "Top up"
  }
}

Validation: Amount must be numeric, min 0.00000001. Our API enforces min $5.00 and max $10,000.00.

10. Freeze / Unfreeze Card

PATCH /api/merchant/baas/customers/{customerUuid}/cards/{cardId}/freeze
Request Body
{
  "freeze": true
}

11. Card Secure Settings

PATCH /api/merchant/baas/customers/{customerUuid}/cards/{cardId}/secure-settings
Request Body
{
  "payload": {
    "contactless_enabled": false
  }
}

12. Close Card

POST /api/merchant/baas/customers/{customerUuid}/cards/{cardId}/close

Currency Accounts

Currency accounts are virtual bank accounts your customers can receive money into. One account per currency per customer.

Create always returns HTTP 201 with an account id. Bank details may be filled right away, or stay empty while status is pending. When the account is ready, Afripay sends currency_account.ready. That applies to every currency, including NGN.

Requirements by currency

Currency What you need What to expect
NGN Customer with BVN (and related identity already on the customer). Often ready in the create response. You still get a ready webhook.
USD / EUR Date of birth;
occupation and source of funds;
passport (with expiry);
selfie;
proof of address (bank_statement or proof_of_address).
May return pending with null bank fields until review finishes. Then ready webhook (or failed if declined).

See Occupation & Source of Funds for the allowed values. If something is missing, the create error lists it.

If create is rejected after review, you get currency_account.failed. That account is removed so you can update documents and try again. Customer records stay in place.

Occupation & source of funds

Send these on create or update customer. They are required before USD or EUR accounts. Use the value in your request; the label is what it means.

Source of funds

  • salary — Salary
  • savings — Savings
Occupation values

Set occupation to a value from this list:

  • software_developer — Software developer
  • registered_nurse — Registered nurse
  • physician — Physician
  • social_worker — Social worker
  • it_specialist — IT specialist
  • customer_service — Customer service
  • financial_analyst — Financial analyst
  • project_manager — Project manager
  • web_developer — Web developer
  • media_professional — Media professional
  • medical_manager — Medical / health services manager
  • lawyer — Lawyer
  • legal_support — Legal support
  • general_manager — General manager
  • market_research — Market research analyst
  • marketing_manager — Marketing manager
  • engineering_technician — Engineering technician
  • accountant — Accountant
  • systems_analyst — Systems analyst
  • other — Other
Other optional fields
  • employment_status
    • employed — Employed
    • self_employed — Self-employed
    • retired — Retired
    • student — Student
    • unemployed — Unemployed
    • homemaker — Homemaker
  • primary_purpose
    • payments_to_friends_or_family_abroad — Payments to friends or family abroad
    • purchase_goods_and_services — Purchase goods and services
    • investment_purposes — Investment
  • monthly_volume
    • 0_4999 — Under 5,000
    • 5000_9999 — 5,000 – 9,999
    • 10000_49999 — 10,000 – 49,999
  • gender
    • male — Male
    • female — Female

13. List Currency Accounts

GET /api/merchant/baas/customers/{customerUuid}/currency-accounts

Optional query: per_page, currency, label.

JSON Response (200 OK)
{
  "status": true,
  "message": "Currency accounts fetched successfully",
  "data": [
    {
      "id": "e2c83407880211f183520e74f6457b17",
      "currency_code": "NGN",
      "status": "active",
      "account_number": "3504939056",
      "account_name": "John Doe",
      "bank_name": "Partner Bank",
      "bank_code": "999",
      "routing_number": null,
      "bank_address": null,
      "rails": ["NIP"],
      "balance": "0.00",
      "label": "Acme/John Doe - NGN",
      "created_at": "2026-07-25T09:40:04Z",
      "customer_id": "1b04b3ffb7f0458db6357e8b152977b9"
    },
    {
      "id": "a9b8c7d6e5f432109876543210fedcba",
      "currency_code": "USD",
      "status": "pending",
      "account_number": null,
      "account_name": null,
      "bank_name": null,
      "bank_code": null,
      "routing_number": null,
      "bank_address": null,
      "rails": ["ACH", "FEDWIRE"],
      "balance": "0.00",
      "label": "Acme/John Doe - USD",
      "created_at": "2026-07-25T09:41:00Z",
      "customer_id": "1b04b3ffb7f0458db6357e8b152977b9"
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "per_page": 20,
    "total": 2
  }
}

14. Create Currency Account

POST /api/merchant/baas/customers/{customerUuid}/currency-accounts
Request Body
{
  "currency": "USD"
}

currency must be 3 letters (for example NGN, USD, EUR). If the account already exists, we return it with HTTP 200.

Response (201 — ready)
{
  "status": true,
  "message": "Currency account created",
  "data": {
    "id": "e2c83407880211f183520e74f6457b17",
    "currency_code": "NGN",
    "status": "active",
    "account_number": "3504939056",
    "account_name": "John Doe",
    "bank_name": "Partner Bank",
    "bank_code": "999",
    "routing_number": null,
    "bank_address": null,
    "rails": ["NIP"],
    "balance": "0.00",
    "label": "Acme/John Doe - NGN",
    "created_at": "2026-07-25T09:40:04Z",
    "customer_id": "1b04b3ffb7f0458db6357e8b152977b9"
  }
}
Response (201 — pending)
{
  "status": true,
  "message": "Currency account created",
  "data": {
    "id": "a9b8c7d6e5f432109876543210fedcba",
    "currency_code": "USD",
    "status": "pending",
    "account_number": null,
    "account_name": null,
    "bank_name": null,
    "bank_code": null,
    "routing_number": null,
    "bank_address": null,
    "rails": ["ACH", "FEDWIRE"],
    "balance": "0.00",
    "label": "Acme/John Doe - USD",
    "created_at": "2026-07-25T09:40:04Z",
    "customer_id": "1b04b3ffb7f0458db6357e8b152977b9"
  }
}

For USD and EUR, bank_address and routing_number appear when the account becomes active. You can call Get Account, or wait for the webhook.

15. Get Currency Account

GET /api/merchant/baas/customers/{customerUuid}/currency-accounts/{accountUuid}
JSON Response (200 OK)
{
  "status": true,
  "message": "Currency account fetched successfully",
  "data": {
    "id": "a9b8c7d6e5f432109876543210fedcba",
    "currency_code": "USD",
    "status": "active",
    "account_number": "120049583091",
    "account_name": "John Doe",
    "bank_name": "Partner Bank",
    "bank_code": "026009593",
    "routing_number": "026009593",
    "bank_address": "Example Bank Address",
    "rails": ["ACH", "FEDWIRE"],
    "balance": "0.00",
    "label": "Acme/John Doe - USD",
    "created_at": "2026-07-25T09:40:04Z",
    "customer_id": "1b04b3ffb7f0458db6357e8b152977b9"
  }
}

16. Update Account

PATCH /api/merchant/baas/customers/{customerUuid}/currency-accounts/{accountUuid}

Not all account types support updates. When supported, send the fields you need to change.

Request Body
{
  "payload": {
    "label": "Updated account label"
  }
}
JSON Response (200 OK)
{
  "status": true,
  "message": "Bank account updated successfully",
  "data": {
    "id": "a9b8c7d6e5f432109876543210fedcba",
    "currency_code": "USD",
    "status": "active",
    "account_number": "120049583091",
    "account_name": "John Doe",
    "bank_name": "Partner Bank",
    "bank_code": "026009593",
    "routing_number": "026009593",
    "bank_address": "Example Bank Address",
    "rails": ["ACH", "FEDWIRE"],
    "balance": "0.00",
    "label": "Updated account label",
    "created_at": "2026-07-25T09:40:04Z",
    "customer_id": "1b04b3ffb7f0458db6357e8b152977b9"
  }
}

Stablecoin Deposit Addresses

Create a crypto deposit address for each customer (USDT or USDC). When they send funds to that address, Afripay credits your USD wallet and notifies you by webhook.

18. List Deposit Addresses

GET /api/merchant/baas/customers/{customerUuid}/deposit-addresses

Optional query: currency, network, per_page (1–100).

Response (200 OK)
{
  "status": true,
  "message": "Deposit addresses fetched successfully",
  "data": [
    {
      "id": "a1b2c3d4e5f6789012345678abcdef01",
      "currency": "USDT",
      "network": "TRC20",
      "address": "TXyzExampleDepositAddress123456",
      "status": "active",
      "settlement_currency": "USD",
      "label": null,
      "created_at": "2026-10-10T12:00:00.000000Z",
      "customer": {
        "id": "1b04b3ffb7f0458db6357e8b152977b9",
        "first_name": "John",
        "last_name": "Doe",
        "email": "john@example.com"
      }
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "per_page": 20,
    "total": 1
  }
}

19. Create Deposit Address

POST /api/merchant/baas/customers/{customerUuid}/deposit-addresses

If this customer already has an address for that currency and network, Afripay returns the existing one instead of creating another.

Request Body
{
  "currency": "USDT",
  "network": "TRC20"
}

Supported currencies: USDT, USDC

Supported networks: TRC20, ERC20, POL, SOL

Response (201 Created)
{
  "status": true,
  "message": "Deposit address ready",
  "data": {
    "id": "a1b2c3d4e5f6789012345678abcdef01",
    "currency": "USDT",
    "network": "TRC20",
    "address": "TXyzExampleDepositAddress123456",
    "status": "active",
    "settlement_currency": "USD",
    "label": null,
    "created_at": "2026-10-10T12:00:00.000000Z",
    "customer": {
      "id": "1b04b3ffb7f0458db6357e8b152977b9",
      "first_name": "John",
      "last_name": "Doe",
      "email": "john@example.com"
    }
  }
}

20. Get Deposit Address

GET /api/merchant/baas/customers/{customerUuid}/deposit-addresses/{depositAddressUuid}

Initiate Stablecoin Payout

Send USDT or USDC from your USD wallet balance to an external address. Afripay debits amount + fee from your USD wallet when the payout is accepted, then sends the transfer on-chain.

Statuses: queued → pending → success or failed.

  • A 201 with queued means the payout was accepted and your wallet was debited — not that the transfer is confirmed on-chain.
  • Treat success / failed from status enquiry or the webhook as final.
  • On failed, Afripay refunds amount + charges to your USD wallet.
POST /api/merchant/baas/stablecoin/payouts
Request Body
{
  "currency": "USDT",
  "network": "TRC20",
  "address": "TXyzExampleDestinationAddress123456",
  "amount": 100.00,
  "reference": "sc_payout_001",
  "remarks": "Vendor payout"
}

Supported currencies: USDT, USDC

Supported networks: TRC20, ERC20, POL, SOL

Response (201 Created)
{
  "status": true,
  "message": "Stablecoin payout initiated and queued.",
  "data": {
    "id": "a1b2c3d4e5f6789012345678abcdef01",
    "reference": "sc_payout_001",
    "trx_id": "SCP1234567890",
    "currency": "USDT",
    "network": "TRC20",
    "address": "TXyzExampleDestinationAddress123456",
    "amount": "100.00",
    "charges": "1.00",
    "status": "queued",
    "tx_hash": null,
    "remarks": "Vendor payout",
    "created_at": "2026-10-16T12:00:00+00:00"
  }
}

Stablecoin Payout Status

GET /api/merchant/baas/stablecoin/payouts/{idOrReference}

Lookup by payout id or your client reference.

Statuses: queued (accepted), pending (in progress), success (confirmed), failed (refunded). Prefer webhooks for final state; poll this endpoint to catch up.

Example Requests
GET /api/merchant/baas/stablecoin/payouts/a1b2c3d4e5f6789012345678abcdef01
GET /api/merchant/baas/stablecoin/payouts/sc_payout_001
Response (200 OK)
{
  "status": true,
  "message": "Stablecoin payout status retrieved successfully",
  "data": {
    "id": "a1b2c3d4e5f6789012345678abcdef01",
    "reference": "sc_payout_001",
    "trx_id": "SCP1234567890",
    "currency": "USDT",
    "network": "TRC20",
    "address": "TXyzExampleDestinationAddress123456",
    "amount": "100.00",
    "charges": "1.00",
    "status": "success",
    "tx_hash": "0xabc...",
    "remarks": "Vendor payout",
    "created_at": "2026-10-16T12:00:00+00:00"
  }
}

Payouts

Send NGN to a Nigerian bank account from your wallet. Responses look like other BAAS endpoints: status, message, and data. Store each payout id.

21. Fetch Support Banks

GET /api/merchant/baas/payouts/banks
Response (200 OK)
{
  "status": true,
  "message": "Banks fetched successfully",
  "data": [
    {
      "bank_code": "090823",
      "bank_name": "Partner Bank"
    },
    {
      "bank_code": "000013",
      "bank_name": "Access Bank"
    }
  ]
}

22. Resolve Account Name

POST /api/merchant/baas/payouts/resolve
Request Body
{
  "account_number": "3504939056",
  "bank_code": "090823"
}
Response (200 OK)
{
  "status": true,
  "message": "Account resolved successfully",
  "data": {
    "account_number": "3504939056",
    "account_name": "Sandbox Individual Account",
    "bank_code": "090823",
    "bank_name": "Partner Bank"
  }
}

23. Initiate Payout

POST /api/merchant/baas/payouts
Request Body
{
  "account_number": "3504939056",
  "bank_code": "090823",
  "amount": 5000.00,
  "reference": "merchant_ref_abc123",
  "remarks": "Services payment"
}
Response (201 Created)
{
  "status": true,
  "message": "Payout initiated and queued.",
  "data": {
    "id": "e2c83407880211f183520e74f6457b17",
    "reference": "merchant_ref_abc123",
    "trx_id": "PO982461972",
    "amount": "5000.00",
    "charges": "50.00",
    "status": "queued",
    "remarks": "Services payment",
    "account_number": "3504939056",
    "bank_code": "090823",
    "bank_name": "Partner Bank",
    "account_name": "Sandbox Individual Account",
    "created_at": "2026-07-25T14:40:00+00:00"
  }
}

24. Payout Status Enquiry

GET /api/merchant/baas/payouts/{idOrReference}

You can look up a payout with the Afripay id returned when you created it, or with your own reference if you sent one.

Response (200 OK)
{
  "status": true,
  "message": "Payout status retrieved successfully",
  "data": {
    "id": "e2c83407880211f183520e74f6457b17",
    "reference": "merchant_ref_abc123",
    "trx_id": "PO982461972",
    "amount": "5000.00",
    "charges": "50.00",
    "status": "success",
    "remarks": "Services payment",
    "account_number": "3504939056",
    "bank_code": "090823",
    "bank_name": "Partner Bank",
    "account_name": "Sandbox Individual Account",
    "created_at": "2026-07-25T14:40:00+00:00"
  }
}

Webhooks

All events go to the URL from Webhook Configuration. Check X-Afripay-Signature on every call (see Verify Webhook Signature). Return HTTP 200 within 15 seconds. Missed deliveries are retried.

Events: charge.success, charge.failed, currency_account.ready, currency_account.failed, usd.deposit.success, stablecoin.deposit.success, stablecoin.payout.status_updated, payout.status_updated.

Checkout

Events: charge.success / charge.failed

JSON Webhook Payload (charge.success)
{
  "event": "charge.success",
  "data": {
    "id": "a1b2c3d4e5f6789012345678abcdef01",
    "reference": "ORDER-982134",
    "afripay_reference": "PL-QTUJNM-AB12CD",
    "trx_id": "PL-64728192",
    "status": "successful",
    "amount": "5000.00",
    "currency": "NGN",
    "fee": "75.00",
    "amount_settled": "4925.00",
    "payment_method": "bank_transfer",
    "customer": {
      "name": "Jane Doe",
      "email": "jane.doe@example.com",
      "phone": "+2348012345678"
    },
    "payment_link": {
      "token": "qtujnmgstm",
      "title": "Payment for Order #982134"
    },
    "metadata": {
      "order_id": 982134,
      "user_id": 42
    },
    "paid_at": "2026-10-06T12:05:00.000000Z",
    "created_at": "2026-10-06T12:00:00.000000Z"
  }
}

Currency Account

Sent for every currency (NGN, USD, EUR, and others you open). The data object matches the create and get account response.

Ready — currency_account.ready

Bank details are set and the account can receive funds. Fired even when NGN (or another currency) is already filled in the create response.

JSON Webhook Payload (currency_account.ready)
{
  "event": "currency_account.ready",
  "data": {
    "id": "a9b8c7d6e5f432109876543210fedcba",
    "currency_code": "USD",
    "status": "active",
    "account_number": "120049583091",
    "account_name": "John Doe",
    "bank_name": "Partner Bank",
    "bank_code": "026009593",
    "routing_number": "026009593",
    "bank_address": "Example Bank Address",
    "rails": ["ACH", "FEDWIRE"],
    "balance": "0.00",
    "label": "Acme/John Doe - USD",
    "created_at": "2026-07-25T09:40:04Z",
    "customer_id": "1b04b3ffb7f0458db6357e8b152977b9"
  }
}

Failed — currency_account.failed

Review did not pass. We include a short reason, then remove that account so you can fix documents and create again. The customer record is kept.

JSON Webhook Payload (currency_account.failed)
{
  "event": "currency_account.failed",
  "data": {
    "id": "a9b8c7d6e5f432109876543210fedcba",
    "currency_code": "USD",
    "status": "failed",
    "account_number": null,
    "account_name": null,
    "bank_name": null,
    "bank_code": null,
    "routing_number": null,
    "bank_address": null,
    "rails": ["ACH", "FEDWIRE"],
    "balance": "0.00",
    "label": "Acme/John Doe - USD",
    "created_at": "2026-07-25T09:40:04Z",
    "customer_id": "1b04b3ffb7f0458db6357e8b152977b9",
    "reason": "Documents require attention. Please update and try again."
  }
}

USD Deposit

Event: usd.deposit.success

Fired when a deposit lands in a USD currency account. This is funding, not account creation.

JSON Webhook Payload
{
  "event": "usd.deposit.success",
  "data": {
    "id": "a1b2c3d4e5f6789012345678abcdef01",
    "reference": "DEP-982134",
    "trx_id": "TX-64728192",
    "status": "successful",
    "amount": "250.00",
    "amount_settled": "250.00",
    "currency": "USD",
    "rail": "ACH",
    "account_number": "120049583091",
    "bank_name": "Partner Bank",
    "routing_number": "026009593",
    "currency_account_id": "a9b8c7d6e5f432109876543210fedcba",
    "customer": {
      "id": "1b04b3ffb7f0458db6357e8b152977b9",
      "first_name": "John",
      "last_name": "Doe",
      "email": "john@example.com"
    },
    "created_at": "2026-10-10T12:05:00.000000Z"
  }
}

Stablecoin Deposit

Event: stablecoin.deposit.success

When a crypto deposit is confirmed, Afripay credits your USD wallet. amount is what the customer sent; amount_settled is what was credited (today these match).

JSON Webhook Payload
{
  "event": "stablecoin.deposit.success",
  "data": {
    "id": "a1b2c3d4e5f6789012345678abcdef01",
    "reference": "SCD1234567890",
    "trx_id": "SCD1234567890",
    "status": "successful",
    "amount": "100.00",
    "amount_settled": "100.00",
    "currency": "USD",
    "network": "TRC20",
    "address": "TXyzExampleDepositAddress123456",
    "deposit_address_id": "a1b2c3d4e5f6789012345678abcdef01",
    "hash": "0xabc...",
    "customer": {
      "id": "1b04b3ffb7f0458db6357e8b152977b9"
    },
    "created_at": "2026-10-10T12:05:00.000000Z"
  }
}

Stablecoin Payout

Event: stablecoin.payout.status_updated

Fired when status moves to pending, success, or failed. On-chain confirmation sets success and usually fills tx_hash. On failed, Afripay refunds amount + charges.

JSON Webhook Payload
{
  "event": "stablecoin.payout.status_updated",
  "data": {
    "id": "a1b2c3d4e5f6789012345678abcdef01",
    "reference": "sc_payout_001",
    "trx_id": "SCP1234567890",
    "currency": "USDT",
    "network": "TRC20",
    "address": "TXyzExampleDestinationAddress123456",
    "amount": "100.00",
    "charges": "1.00",
    "status": "success",
    "tx_hash": "0xabc...",
    "remarks": "Vendor payout",
    "created_at": "2026-10-16T12:00:00+00:00"
  }
}

NGN Payout

Event: payout.status_updated

Use id to match the payout and status for the outcome. Status values: queued, pending, success, failed.

JSON Webhook Payload
{
  "event": "payout.status_updated",
  "data": {
    "id": "e2c83407880211f183520e74f6457b17",
    "reference": "merchant_ref_abc123",
    "trx_id": "PO982461972",
    "amount": "5000.00",
    "charges": "50.00",
    "status": "success",
    "remarks": "Services payment",
    "account_number": "3504939056",
    "bank_code": "090823",
    "bank_name": "Partner Bank",
    "account_name": "Sandbox Individual Account",
    "created_at": "2026-07-25T14:40:00+00:00"
  }
}

Transactions

Fetch your payment history. Each item includes an id (use this to identify the transaction in your system) and a short trx code (suitable to show on receipts or to customers).

25. List Transactions

GET /api/merchant/transactions/{slug?}

Filter by type (optional slug)

SlugWhat it returns
money_outWithdrawals and payouts
merchant_paymentPayments received by your merchant account
received_paymentPayments from Afripay wallet users
pay_linkPayments made through payment links
Example item (money_out)
{
  "id": "e2c83407880211f183520e74f6457b17",
  "trx": "PO982461972",
  "transaction_type": "MONEY-OUT",
  "request_amount": "5000.00 NGN",
  "status": "Pending",
  "date_time": "2026-07-25T14:40:00.000000Z"
}

Error Responses

401 Unauthorized

{
  "status": false,
  "message": "Client credentials are required.",
  "data": null
}

422 Validation Error

{
  "status": false,
  "message": "The bvn field is required.",
  "data": {
    "errors": {
      "bvn": ["The bvn field is required."],
      "address": ["The address field is required."]
    }
  }
}