REST API v1
Dynamic Provider Switching

EgoPayHQ
Developer API

Integrate the platform's virtual account creation and payout infrastructure directly into your own app. Whether you're building a fintech product, a cooperative management system, or a savings platform — our API gives you everything you need.

Virtual Accounts

Assign unique NGN virtual accounts to your users for deposits

Payouts

Send money to any Nigerian bank account via Paystack or URE MFB

Webhooks

Get real-time event notifications for deposits and payouts

Dynamic Provider Switching

The platform supports multiple banking infrastructure providers. The platform admin can switch the active virtual account provider at any time — your API calls stay exactly the same. The backend automatically routes to the correct provider.

URE
MFB

URE Microfinance Bank

Default

Requires only BVN + name. Simpler integration, faster onboarding. Powered by Lenco infrastructure.

MatrixPay (CashMatrix)

Requires BVN, name, email and phone. Asset Matrix MFB. Recommended for full KYC flows.

The active provider is set by the platform admin in Platform Settings → Virtual Account Provider. Your API code never needs to change — the provider field in each response tells you which backend was used.

Live API Endpoints

Virtual Accountshttps://bkzurgwkozsnaauztghh.supabase.co/functions/v1/egopay-virtual-accounts
Payoutshttps://bkzurgwkozsnaauztghh.supabase.co/functions/v1/egopay-payouts
Customershttps://bkzurgwkozsnaauztghh.supabase.co/functions/v1/egopay-customers
Transactionshttps://bkzurgwkozsnaauztghh.supabase.co/functions/v1/egopay-transactions

All endpoints are live Supabase Edge Functions. Requests must be made over HTTPS with a valid Authorization: Bearer ehq_live_... API key header.

Supabase Direct Connection

Connect your app directly using the Supabase client SDK

Project URLhttps://bkzurgwkozsnaauztghh.supabase.co
ANON KeyGet from your Dashboard → API Keys

Use these credentials with the @supabase/supabase-js client in your app. The ANON key is public-safe — RLS policies protect your data. Find these in your Dashboard → API Keys.

Authentication

The platform uses API keys to authenticate all requests. You can generate and manage your API keys from the Dashboard → API Keys section. Keep your keys secret — never expose them in client-side code or public repositories.

Required Headers

AuthorizationBearer live_YOUR_API_KEYRequired. Your org API key (ehq_live_... or ehq_test_...)
X-Org-IDyour-org-slugOptional. Your organization slug. The API key already scopes the request to your org.
Content-Typeapplication/jsonRequired for POST/PATCH requests
curl -X GET https://bkzurgwkozsnaauztghh.supabase.co/functions/v1/egopay-customers \
  -H "Authorization: Bearer live_YOUR_API_KEY" \
  -H "X-Org-ID: your-org-slug" \
  -H "Content-Type: application/json"

Test Keys

Keys prefixed with ehq_test_ use sandbox mode. No real money moves. Payouts return fake success responses. VA creation returns sandbox account numbers. Use these during development.

Live Keys

Keys prefixed with ehq_live_ process real transactions. Only use in production environments.

CORS & Browser Requests

All endpoints allow cross-origin requests (Access-Control-Allow-Origin: *). You can call the API directly from browser JavaScript. However, never expose your API key in client-side code — use a backend proxy or the Supabase client SDK with RLS instead.

Pagination

All list endpoints return paginated results. Use page and limit query parameters to navigate through results.

Query Parameters

pageintegerPage number. Starts at 1. Default: 1.
limitintegerResults per page. Maximum: 100. Default: 20.
// Response meta object (included in all list responses)
{
  "status": "success",
  "data": [...],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 142,
    "has_more": true
  }
}

First page

GET /egopay-customers?page=1&limit=20

Next page

GET /egopay-customers?page=2&limit=20

Maximum results

GET /egopay-customers?page=1&limit=100

Virtual Accounts

Virtual accounts allow your users to receive NGN deposits via bank transfer. Each customer gets a unique account number. When a deposit arrives, the platform fires a va.deposit.received webhook to your endpoint.

Payouts

Initiate NGN payouts to any Nigerian bank account. Payouts are processed via Paystack and typically settle within minutes. You can send single or bulk payouts.

Amount Format — Kobo, Not Naira

The egopay-payouts API expects amounts in kobo (the smallest NGN unit). ₦1 = 100 kobo. So ₦5,000 = 500000. The paystack-transfer internal function (for portal transfers) expects amounts in Naira instead.

Idempotency — Unique References Required

Every payout and transfer requires a unique reference. If you send the same reference twice, the API returns DUPLICATE_REFERENCE (HTTP 409). This prevents accidental double-payments. If you omit reference, one is auto-generated for you.

How Narration Works on Bank Statements

Format

"SenderName by OrgName"

The sender_name field controls the first part. Your organization name is always appended automatically.

With sender_name

"Adebanjo Olusayo by Signature Africa"

Recipient sees who sent it

Without sender_name

"Chukwuemeka Nwosu by Signature Africa"

Falls back to account_name (recipient)

Common Nigerian Bank Codes

Access Bank044
First Bank011
GTBank058
UBA033
Zenith Bank057
Wema Bank035
Kuda Bank090267
OPay100004
Moniepoint50515
Stanbic IBTC221
Fidelity Bank070
Union Bank032

Portal Transfers (Customer-Initiated)

When your customers initiate transfers from the customer portal, the transfer is routed through the paystack-transfer Supabase Edge Function — not the egopay-payouts API. This section documents how that flow works, how sender_name is applied, and how the narration appears on the recipient's bank statement regardless of whether Paystack or Lenco (URE Microfinance Bank) is the active payout provider.

Same Narration Format — Both Providers

Whether the platform admin has configured Paystack or Lenco (URE Microfinance Bank) as the active payout provider, the narration format on the recipient's bank statement is always identical:

"SenderName by OrgName"

Paystack Provider

Transfer goes via Paystack's transfer API. The reason field is set to the formatted narration string.

URE
MFB

Lenco Provider

Transfer goes via Lenco's transfer API. The narration field is set to the same formatted string.

Lenco-Specific Behaviour

Account Name Verification

Lenco performs strict account name verification before processing a transfer. If the name doesn't match exactly, the transfer will fail with code ACCOUNT_NAME_VERIFICATION_FAILED. In this case, you can retry with skip_name_verification: true to bypass the check.

Narration Field

Lenco uses the narration field (not reason like Paystack). The platform handles this mapping automatically — you always pass sender_name and the backend formats it correctly for whichever provider is active.

Amount Format

Lenco expects amounts in Naira (not kobo). The platform converts automatically — you always pass amounts in kobo to egopay-payouts or in Naira to paystack-transfer. No conversion needed on your end.

Customers

Manage your end-users (customers) within the platform. Each customer can have a virtual account, KYC status, and transaction history.

Transactions

Query transaction history for your organization or individual customers. Transactions include deposits (credits to virtual accounts) and payouts (debits).

Webhooks

The platform sends webhook events to your server when important things happen — like a deposit arriving on a virtual account or a payout completing. Register your endpoint in the Dashboard → Webhooks section.

Event Types

va.createdA virtual account was created for a customer
va.deposit.receivedA deposit arrived on a virtual account
payout.successA payout was successfully delivered
payout.failedA payout failed to deliver
customer.createdA new customer was created

Webhook Payload Structure

{
  "event": "va.deposit.received",
  "timestamp": "2026-04-18T11:30:00Z",
  "org_id": "your-org-slug",
  "data": {
    "id": "txn_1a2b3c",
    "customer_id": "cust_abc123",
    "account_number": "9012345678",
    "account_name": "AMAKA OBI",
    "bank_name": "Wema Bank",
    "amount": 50000,
    "currency": "NGN",
    "reference": "MTRX-20260418-001",
    "provider": "ure_microfinance_bank",
    "status": "success",
    "created_at": "2026-04-18T11:30:00Z"
  }
}

Verifying Webhook Signatures

Every webhook request includes an X-EgopayHQ-Signature header. Verify this to ensure the request came from the platform and not a third party.

import crypto from "crypto";

function verifyWebhook(payload: string, signature: string, secret: string): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// In your Express handler:
app.post("/webhooks/platform", (req, res) => {
  const sig = req.headers["x-egopayhq-signature"] as string;
  const raw = JSON.stringify(req.body);

  if (!verifyWebhook(raw, sig, process.env.PLATFORM_WEBHOOK_SECRET!)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const { event, data } = req.body;

  if (event === "va.deposit.received") {
    // Credit the customer's wallet in your app
    await creditUserWallet(data.customer_id, data.amount);
  }

  res.json({ received: true });
});

Errors & Codes

The platform uses standard HTTP status codes. All error responses include a code and message field.

// Error response format
{
  "status": "error",
  "code": "INSUFFICIENT_BALANCE",
  "message": "Your organization wallet has insufficient balance for this payout.",
  "details": {
    "required": 500000,
    "available": 320000
  }
}
200OKRequest succeeded.
201CreatedResource was created successfully.
400Bad RequestMissing or invalid parameters. Check the error message for details.
401UnauthorizedInvalid or missing API key. Check your Authorization header.
403ForbiddenYour API key doesn't have permission for this action.
404Not FoundThe requested resource doesn't exist.
409ConflictDuplicate reference or resource already exists.
422UnprocessableRequest was valid but couldn't be processed (e.g. BVN mismatch).
429Rate LimitedReserved for future rate limiting. Not currently enforced.
500Server ErrorSomething went wrong on our end. Contact support if this persists.

Common Error Codes

INVALID_API_KEYThe API key is invalid or has been revoked
INSUFFICIENT_BALANCEOrg wallet balance is too low for this payout
MISSING_FIELDSRequired fields are missing from the request body
DUPLICATE_REFERENCEA transaction with this reference already exists. All references must be unique.
DUPLICATE_EMAILA customer with this email already exists in your organization
DUPLICATE_VIRTUAL_ACCOUNTCustomer already has a virtual account assigned
CUSTOMER_NOT_FOUNDNo customer found with the given ID
NOT_FOUNDThe requested resource does not exist
FORBIDDENYour API key lacks permission for this action
DB_ERRORA database error occurred. Retry the request.
METHOD_NOT_ALLOWEDThe HTTP method is not supported for this endpoint
ACCOUNT_NAME_VERIFICATION_FAILEDLenco could not verify the account name. Retry with skip_name_verification: true if the account is confirmed correct.
LENCO_ERRORA Lenco provider error occurred
PAYSTACK_ERRORA Paystack provider error occurred
LENCO_RESOLVE_FAILEDLenco could not verify this account
RESOLUTION_FAILEDCould not resolve the bank account. Check account number and bank code.
NO_PROVIDERNo payment provider is configured. Contact your admin.
AMOUNT_TOO_LOWAmount is below the minimum allowed (₦100 = 10,000 kobo)
AMOUNT_TOO_HIGHAmount exceeds the maximum allowed (₦5,000,000 = 500,000,000 kobo)
INVALID_RECIPIENTInvalid recipient details. Verify account number and bank.
INVALID_ACCOUNT_NUMBERAccount number must be exactly 10 digits
TIMEOUTThe provider request timed out. Retry later.
NETWORK_ERRORNetwork error communicating with the provider. Retry later.
KYC_REQUIREDCustomer must complete KYC before this action
ACCOUNT_FROZENThe virtual account has been frozen
INVALID_BANK_CODEThe bank code provided is not recognized

Additional APIs

The platform provides additional APIs for bill payments, payment links, POS terminals, and subscription management. These are available as Supabase Edge Functions and can be invoked via the Supabase client SDK.

VTU & Bill Payments

vtunaija-bills

Purchase airtime, data bundles, and pay utility bills (electricity, cable TV) directly from your app. Supports MTN, Airtel, Glo, 9mobile, and major utility providers.

Supported Actions

buy_airtimebuy_datapay_electricitypay_cable

Payment Links

payment-link

Generate shareable payment links for one-off collections. Customers click the link, enter their card or bank details, and complete payment. Perfect for invoices, donations, or event tickets.

Supported Actions

create_linkget_linklist_linksdeactivate_link

POS Terminals

lenco-pos

Manage physical POS terminals for in-person card payments. Create terminals, assign them to agents, and track transaction history. Webhook events are sent for each POS transaction.

Supported Actions

create_terminallist_terminalsget_terminalassign_agentprocess_transaction

Subscription Payments

subscription-payment

Set up recurring billing for your customers. Create subscription plans, manage customer subscriptions, and handle automatic renewals. Supports weekly, monthly, and yearly billing cycles.

Supported Actions

create_planget_planlist_planssubscribe_customercancel_subscription

How to Call Additional APIs

These functions are invoked via the Supabase client SDK, not the REST API directly. Use supabase.functions.invoke() with the function name and action.

const { data, error } = await supabase.functions.invoke("vtunaija-bills", {
  body: {
    action: "buy_airtime",
    phone: "+2348012345678",
    amount: 1000,
    network: "mtn",
    reference: "AIRTIME-001"
  }
});

SDKs & Libraries

Official SDKs are coming soon. In the meantime, use the REST API directly with the code examples above. Here's a minimal TypeScript wrapper to get you started quickly in Node.js:

// platform-sdk.ts — minimal SDK wrapper (production-ready)
const BASE = "https://bkzurgwkozsnaauztghh.supabase.co/functions/v1";

class PlatformSDK {
  private headers: Record<string, string>;

  constructor({ apiKey, orgId }: { apiKey: string; orgId: string }) {
    this.headers = {
      "Authorization": `Bearer ${apiKey}`,
      "X-Org-ID": orgId,
      "Content-Type": "application/json",
    };
  }

  private async request<T>(method: string, fn: string, path = "", body?: unknown): Promise<T> {
    const res = await fetch(`${BASE}/${fn}${path}`, {
      method,
      headers: this.headers,
      body: body ? JSON.stringify(body) : undefined,
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.message ?? "API error");
    return data.data as T;
  }

  customers = {
    create: (body: unknown) => this.request("POST", "egopay-customers", "", body),
    get: (id: string) => this.request("GET", "egopay-customers", `/${id}`),
    list: (params?: Record<string, string>) =>
      this.request("GET", "egopay-customers", `?${new URLSearchParams(params)}`),
    getKyc: (id: string) => this.request("GET", "egopay-customers", `/${id}/kyc`),
  };

  virtualAccounts = {
    create: (body: unknown) => this.request("POST", "egopay-virtual-accounts", "", body),
    get: (id: string) => this.request("GET", "egopay-virtual-accounts", `/${id}`),
    list: (params?: Record<string, string>) =>
      this.request("GET", "egopay-virtual-accounts", `?${new URLSearchParams(params)}`),
  };

  payouts = {
    create: (body: unknown) => this.request("POST", "egopay-payouts", "", body),
    bulk: (payouts: unknown[]) => this.request("POST", "egopay-payouts", "/bulk", { payouts }),
    get: (id: string) => this.request("GET", "egopay-payouts", `/${id}`),
    list: (params?: Record<string, string>) =>
      this.request("GET", "egopay-payouts", `?${new URLSearchParams(params)}`),
    // Resolve a bank account name before sending a payout
    resolve: (accountNumber: string, bankCode: string) =>
      this.request("GET", "egopay-payouts", `/resolve?account_number=${accountNumber}&bank_code=${bankCode}`),
    // Get the full list of supported Nigerian banks (cached 24h)
    banks: () => this.request("GET", "egopay-payouts", "/banks"),
  };

  transactions = {
    list: (params?: Record<string, string>) =>
      this.request("GET", "egopay-transactions", `?${new URLSearchParams(params)}`),
    get: (id: string) => this.request("GET", "egopay-transactions", `/${id}`),
  };
}

// Usage — these are LIVE endpoints:
const sdk = new PlatformSDK({
  apiKey: process.env.PLATFORM_API_KEY!,   // live_... from your dashboard
  orgId: process.env.PLATFORM_ORG_ID!,     // your org slug e.g. "acme-finance"
});

// 1. Create a customer
const customer = await sdk.customers.create({
  full_name: "Amaka Obi",
  email: "amaka@example.com",
  phone: "+2348012345678",
  bvn: "22345678901",
});

// 2. Assign a virtual account
const account = await sdk.virtualAccounts.create({
  customer_id: customer.id,
  bvn: "22345678901",
  first_name: "Amaka",
  last_name: "Obi",
  phone: "+2348012345678",
  email: "amaka@example.com",
});

console.log(account.account_number); // "9012345678"
console.log(account.bank_name);      // "Wema Bank"

// The provider field tells you which banking backend was used.
// This is controlled by the platform admin — your code never needs to change.
console.log(account.provider);
// "ure_microfinance_bank"  → URE Microfinance Bank (via Lenco infrastructure)
// "matrixpay"             → Asset Matrix MFB (via CashMatrix)

// 3. Filter VA list by provider — useful for auditing or migration
const ureAccounts = await sdk.virtualAccounts.list({
  provider: "ure_microfinance_bank",
  limit: "50",
});
const matrixAccounts = await sdk.virtualAccounts.list({
  provider: "matrixpay",
});

// Each item in the list carries the provider field:
ureAccounts.forEach((va: { account_number: string; provider: string }) => {
  console.log(va.account_number, va.provider);
  // "9012345678"  "ure_microfinance_bank"
});

// 4. Initiate a payout
// sender_name controls the first part of the bank statement narration:
// "Adebanjo Olusayo Ayowole by YourOrgName"
const payout = await sdk.payouts.create({
  amount: 5000000,          // ₦50,000 in kobo
  bank_code: "058",
  account_number: "0123456789",
  account_name: "Chukwuemeka Nwosu",
  sender_name: "Adebanjo Olusayo Ayowole", // shown on recipient's bank statement
  narration: "Savings withdrawal",
});

console.log(payout.status);    // "success" | "pending"
console.log(payout.narration); // "Adebanjo Olusayo Ayowole by YourOrgName"

Ready to start building?

Register your organization, generate your API key, and go live in minutes.