Home
API documentation

Developer docs

Topify API reference

Integration guide for merchant APIs, webhooks, and dashboard authentication. Amounts are integers in kobo (₦1 = 100 kobo).

API base URL: https://apipay.topify.ng

Import into Postman

Download the collection and environment below — they mirror every endpoint on this page. Set api_base_url to https://apipay.topify.ng (not this docs site URL).

Try it — API playground

Enter your keys once, then hit Send on any endpoint below to run it live. Values are stored only in your browser (localStorage).

For dashboard (bearer) endpoints. Auto-filled if you're logged in.

For merchant /api/v1 (API key) endpoints.

For the popup checkout endpoints.

Overview

Two API surfaces

Dashboard API

Sanctum bearer token from login, OTP, or Google OAuth. Powers the merchant dashboard at /api/*.

Merchant API (v1)

Secret key as Bearer on https://apipay.topify.ng/api/v1/* for server-to-server integrations.

Authentication

Merchant API keys

Send your secret key on every External v1 request. Obtain keys from Developer settings.

Authorization: Bearer sk_test_xxxxxxxx
Accept: application/json
Content-Type: application/json
sk_test_Test mode (default for new keys)
sk_live_Live mode after account approval

Response format

All JSON responses use a standard envelope. Validation errors return HTTP 422 with field messages in errors.

{
  "status": true,
  "message": "Human-readable summary",
  "data": { },
  "errors": null
}

Customers

Manage payer identities before reserving virtual accounts.

POST/api/v1/customers
API key

Create customer

Creates a merchant-scoped customer. If the email already exists, returns the existing record.

FieldTypeDescription
email*stringCustomer email
name*stringFull name
phonestringNigerian mobile (080… or +234…)
metaobjectOptional key-value metadata

Request

POST https://apipay.topify.ng/api/v1/customers
Authorization: Bearer sk_test_xxxxxxxx
Content-Type: application/json

{
  "email": "buyer@example.com",
  "name": "Ada Okafor",
  "phone": "08012345678"
}

Response

{
  "status": true,
  "message": "Customer created.",
  "data": {
    "customer_code": "CUS-ABC123",
    "email": "buyer@example.com",
    "name": "Ada Okafor",
    "phone": "08012345678",
    "meta": null
  },
  "errors": null
}
GET/api/v1/customers
API key

List customers

Paginated list of customers for the authenticated merchant.

FieldTypeDescription
per_pageintegerPage size (default 15)

Request

GET https://apipay.topify.ng/api/v1/customers?per_page=15
Authorization: Bearer sk_test_xxxxxxxx
GET/api/v1/customers/{customer_code}
API key

Get customer

Fetch a single customer by their customer code.

Request

GET https://apipay.topify.ng/api/v1/customers/CUS-ABC123
Authorization: Bearer sk_test_xxxxxxxx

Virtual accounts

Reserve dedicated NUBANs for wallet funding and per-customer collections.

Platform services

Live status from admin settings. Disabled providers are rejected by the API and hidden from merchant dashboards.

Loading status…

Inbound transfers (OPay, bank apps, etc.) credit the wallet after a platform fee is deducted. Fees are configurable in admin settings (flat, percentage, and cap).

When a payment lands, you receive charge.success and wallet.funded webhooks. The transaction amount is the gross transfer; fee is what was retained.

Merchant API (v1)

POST/api/v1/virtual-accounts/reserve
API key

Reserve virtual accounts

Provisions dedicated NUBANs for a customer. Idempotent per provider + email + name + phone — repeat calls return existing active accounts without calling the provider again.

FieldTypeDescription
email*stringCustomer email (idempotency key)
name*stringCustomer full name
phoneNumberstringNigerian mobile number
bankCode*string[]Provider bank codes (see Bank codes)
businessId*stringApproved business ID from dashboard
identity_document_type*stringRequired for PalmPay (20946): bvn, nin, or cac
identity_number*stringBVN/NIN: 11 digits. CAC: RC or BN prefix (e.g. RC1234567)
useLivebooleanOptional. Force live provider credentials when platform sandbox is on.
  • Requires an approved business (businessId must belong to your merchant account).
  • Invalid businessId returns HTTP 422; valid but unapproved business returns HTTP 403.
  • identity_document_type and identity_number are required when bankCode includes PalmPay (20946).
  • Each bankCode provisions a separate account for the same customer identity.
  • New accounts trigger the customer.virtual_account.created webhook.
  • Inbound payments trigger charge.success and wallet.funded; wallet is credited net of platform fee.

Request

POST https://apipay.topify.ng/api/v1/virtual-accounts/reserve
Authorization: Bearer sk_test_xxxxxxxx
Content-Type: application/json

{
  "email": "buyer@example.com",
  "name": "Ada Okafor",
  "phoneNumber": "08012345678",
  "bankCode": ["20946", "30901"],
  "businessId": "TPYBIZXXXXXXXXXXXX",
  "identity_document_type": "bvn",
  "identity_number": "22334455667"
}

Response

{
  "status": true,
  "message": "Virtual accounts reserved.",
  "data": {
    "customer": {
      "customer_code": "CUS-ABC123",
      "email": "buyer@example.com",
      "name": "Ada Okafor"
    },
    "accounts": [
      {
        "provider": "palmpay",
        "account_number": "6600123456",
        "bank_name": "PalmPay MFB",
        "account_name": "ADA OKAFOR",
        "is_active": true,
        "is_sandbox": false,
        "customer_email": "buyer@example.com"
      }
    ]
  },
  "errors": null
}
GET/api/v1/virtual-accounts/customer/{customer_code}
API key

List customer accounts

Returns all active reserved accounts linked to a customer.

Request

GET https://apipay.topify.ng/api/v1/virtual-accounts/customer/CUS-ABC123
Authorization: Bearer sk_test_xxxxxxxx

Response

{
  "status": true,
  "message": "Customer virtual accounts.",
  "data": [
    {
      "provider": "palmpay",
      "account_number": "6600123456",
      "bank_name": "PalmPay MFB",
      "account_name": "ADA OKAFOR",
      "is_active": true,
      "is_sandbox": false,
      "customer_email": "buyer@example.com"
    }
  ],
  "errors": null
}

Dashboard API (Sanctum)

Used by the reserved account page in the merchant dashboard. Authenticate with the Sanctum bearer token from login.

GET/api/virtual-accounts
Bearer token

List your reserved accounts

Returns all active virtual accounts provisioned for the logged-in merchant user.

Request

GET https://apipay.topify.ng/api/virtual-accounts
Authorization: Bearer <sanctum_token>

Response

{
  "status": true,
  "message": "Virtual accounts.",
  "data": [
    {
      "id": 1,
      "provider": "palmpay",
      "account_number": "6678434586",
      "bank_name": "PalmPay",
      "account_name": "Jane Doe",
      "is_active": true,
      "is_sandbox": false,
      "replaceable": false,
      "created_at": "2026-06-10T12:00:00+00:00"
    }
  ],
  "errors": null
}
GET/api/virtual-accounts/provider-modes
Bearer token

Provider sandbox modes

Shows whether each provider is enabled, and sandbox vs live mode (mirrors admin settings). Public equivalent: GET /api/platform/providers.

Request

GET https://apipay.topify.ng/api/virtual-accounts/provider-modes
Authorization: Bearer <sanctum_token>
POST/api/virtual-accounts/provision
Bearer token

Provision reserved account

Creates a dedicated NUBAN for the authenticated user. Idempotent per provider + customer identity.

FieldTypeDescription
provider*stringpalmpay or monnify (Moniepoint). Must be enabled in admin.
customer_name*stringAccount holder name
email*stringContact email sent to provider
phone*stringNigerian mobile number
identity_document_typestringPalmPay only: bvn, nin, or cac
identity_numberstringPalmPay identity number
  • Inbound transfers to the NUBAN credit the wallet automatically via provider webhook.
  • Platform fee is deducted from the gross transfer before crediting the wallet.

Request

POST https://apipay.topify.ng/api/virtual-accounts/provision
Authorization: Bearer <sanctum_token>
Content-Type: application/json

{
  "provider": "palmpay",
  "customer_name": "Jane Doe",
  "email": "jane@example.com",
  "phone": "08012345678",
  "identity_document_type": "bvn",
  "identity_number": "22334455667"
}
GET/api/virtual-accounts/{provider}
Bearer token

Get account by provider

Returns the user's virtual account for a specific provider, or 404 if not provisioned.

Request

GET https://apipay.topify.ng/api/virtual-accounts/palmpay
Authorization: Bearer <sanctum_token>

KYC Verification

Submit verification requests for BVN or NIN identifiers and charge consumers accordingly.

Verification requests query the database (Identity Pool) cache first to save costs. If a verified record matching the submitted name, phone number, and birth date is found, the system skips the external Monnify provider call.

Merchant wallets are debited the configured charge amount in kobo for all verification attempts. Setup verification charges under Admin settings.

POST/api/v1/kyc/verify
API key

Verify BVN/NIN

Performs a real-time BVN or NIN verification for a user. Debits the merchant's wallet for the check fee configured in the admin dashboard (charges are set per BVN/NIN). Local caching is automatically applied to save external provider costs: if the identifier has already been verified and matches the submitted details, the API resolves from the local identity pool.

FieldTypeDescription
kyc_type*stringType of identification to verify: 'bvn' or 'nin'
identifier*string11-digit BVN or NIN code
name*stringFull name to verify against the registry (e.g. John Doe)
phone*stringPhone number associated with the identity
date_of_birthstringOptional. Date of birth in YYYY-MM-DD format
  • Each request is charged from the merchant's dashboard wallet balance.
  • If the wallet balance is less than the verification charge, the request returns HTTP 402.
  • If name/phone details contradict existing verified records in the database, the request returns HTTP 422 immediately with the conflict fields and still debits the wallet.

Request

POST https://apipay.topify.ng/api/v1/kyc/verify
Authorization: Bearer sk_test_xxxxxxxx
Content-Type: application/json

{
  "kyc_type": "bvn",
  "identifier": "12345678901",
  "name": "John Doe",
  "phone": "08012345678",
  "date_of_birth": "1990-04-12"
}

Response

{
  "status": true,
  "message": "Identity verified successfully (provider verification).",
  "data": {
    "kyc_type": "bvn",
    "identifier_mask": "*******8901",
    "verified": true,
    "source": "provider",
    "name_match": "FULL_MATCH",
    "phone_match": "FULL_MATCH",
    "dob_match": "FULL_MATCH"
  },
  "errors": null
}

One-time transfers

Generate temporary PalmPay bank transfer accounts for single transactions.

One-time transfers allow you to initiate a payment flow where a customer receives a temporary bank account number to pay the exact amount. Once the transfer lands, your wallet is credited net of fees, and a charge.success webhook is sent to your platform.

Note: Temporary accounts are subject to automatic expiration (typically 30 minutes).

POST/api/v1/payments/create-transfer
API key

Create one-time transfer payment

Generates a temporary PalmPay bank account for a single transaction. Customers pay the exact amount to the bank details before expiration.

FieldTypeDescription
amount*integerPayment amount in kobo (minimum 100 kobo)
customer_name*stringCustomer full name
customer_email*stringCustomer email
reference*stringUnique merchant transaction reference
  • The generated account number has a fixed expiry time (typically 30 minutes).
  • Merchant wallet is credited net of transaction fees when settlement webhook lands.

Request

POST https://apipay.topify.ng/api/v1/payments/create-transfer
Authorization: Bearer sk_test_xxxxxxxx
Content-Type: application/json

{
  "amount": 500000,
  "customer_name": "John Doe",
  "customer_email": "john@example.com",
  "reference": "INV-123456"
}

Response

{
  "status": true,
  "message": "Transfer payment account generated successfully.",
  "data": {
    "reference": "INV-123456",
    "account_number": "9988776655",
    "account_name": "John Doe - Topify",
    "bank_name": "PalmPay MFB",
    "expires_at": "2026-06-24 21:15:35"
  },
  "errors": null
}
GET/api/v1/payments/{reference}
API key

Get transfer payment status

Fetch the payment details and current transaction status by merchant reference.

  • Returned status can be: pending, processing, successful, failed, or expired.
  • If the current time has passed expires_at and no payment has landed, status will return expired.

Request

GET https://apipay.topify.ng/api/v1/payments/INV-123456
Authorization: Bearer sk_test_xxxxxxxx

Response

{
  "status": true,
  "message": "Payment status retrieved successfully.",
  "data": {
    "reference": "INV-123456",
    "status": "pending",
    "amount": 500000,
    "provider": "palmpay",
    "expires_at": "2026-06-24 21:15:35"
  },
  "errors": null
}

Transactions

GET/api/v1/transactions/verify/{reference}
API key

Verify transaction

Look up a transaction by reference. Only returns transactions belonging to the authenticated merchant.

  • Amount and fee are integers in kobo (₦1 = 100 kobo).

Request

GET https://apipay.topify.ng/api/v1/transactions/verify/TXN-ABC123
Authorization: Bearer sk_test_xxxxxxxx

Response

{
  "status": true,
  "message": "Transaction details.",
  "data": {
    "reference": "TXN-ABC123",
    "type": "card_payment",
    "amount": 5000000,
    "fee": 75000,
    "status": "success",
    "provider": "palmpay",
    "meta": {},
    "created_at": "2026-06-06T12:00:00+00:00"
  },
  "errors": null
}

Webhooks

Configure your endpoint under Developer. Each POST includes X-Topify-Signature — HMAC-SHA512 of the raw body using your webhook secret.

// Node.js
import crypto from "node:crypto";

const expected = crypto
  .createHmac("sha512", process.env.TOPIFY_WEBHOOK_SECRET)
  .update(rawRequestBody)
  .digest("hex");

if (expected !== request.headers["x-topify-signature"]) {
  return res.status(401).send("invalid signature");
}

Event types

  • customer.virtual_account.created
  • charge.success
  • charge.failed
  • wallet.funded
  • wallet.transfer.sent
  • wallet.transfer.received
  • card_payment.success
  • card_payment.failed
  • kyc.status.updated
  • business.status.updated
  • transaction.reversed

See Webhook configuration for delivery history and retries (up to 10 attempts).

Bank codes

CodeProviderStatus
20946PalmPay
30901Moniepoint
208979PSB

Sandbox & live

Platform services

Live status from admin settings. Disabled providers are rejected by the API and hidden from merchant dashboards.

Loading status…

Platform administrators control per-provider availability and sandbox mode (PalmPay and Moniepoint independently). Disabled providers are hidden from dashboards and rejected by reserve/provision APIs. When sandbox is enabled, reserve and dashboard provisioning use test NUBANs — no real money moves.

The dashboard Reserved account page follows admin sandbox settings automatically. On the External API, pass useLive: true only when you need to force live credentials while sandbox is on.

Sandbox accounts are labelled with is_sandbox: true in API responses.

Integration checklist

  1. Create API credentials in test mode.
  2. Complete business onboarding and note your businessId.
  3. POST /api/v1/virtual-accounts/reserve with customer details and bank codes.
  4. Display account_number and bank_name to your customer.
  5. Listen for customer.virtual_account.created and charge.success.
  6. Verify payments via GET /api/v1/transactions/verify/{reference} when needed.
  7. Switch to live keys after go-live approval.

Postman collection

Use the download buttons at the top of this page, or import from backend/postman/TopifyGateway.Docs.postman_collection.json. Run Setup first (Login → API credentials → Business status), then the Merchant or Dashboard API folders.