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.
MFB
URE Microfinance Bank
DefaultRequires 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
https://bkzurgwkozsnaauztghh.supabase.co/functions/v1/egopay-virtual-accountshttps://bkzurgwkozsnaauztghh.supabase.co/functions/v1/egopay-payoutshttps://bkzurgwkozsnaauztghh.supabase.co/functions/v1/egopay-customershttps://bkzurgwkozsnaauztghh.supabase.co/functions/v1/egopay-transactionsAll 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.coANON KeyGet from your Dashboard → API KeysUse 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 requestscurl -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
page | integer | Page number. Starts at 1. Default: 1. |
limit | integer | Results 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=20Next page
GET /egopay-customers?page=2&limit=20Maximum results
GET /egopay-customers?page=1&limit=100Virtual 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
04401105803305703509026710000450515221070032Portal 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:
Paystack Provider
Transfer goes via Paystack's transfer API. The reason field is set to the formatted narration string.
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 customerva.deposit.receivedA deposit arrived on a virtual accountpayout.successA payout was successfully deliveredpayout.failedA payout failed to delivercustomer.createdA new customer was createdWebhook 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
}
}Common Error Codes
INVALID_API_KEYThe API key is invalid or has been revokedINSUFFICIENT_BALANCEOrg wallet balance is too low for this payoutMISSING_FIELDSRequired fields are missing from the request bodyDUPLICATE_REFERENCEA transaction with this reference already exists. All references must be unique.DUPLICATE_EMAILA customer with this email already exists in your organizationDUPLICATE_VIRTUAL_ACCOUNTCustomer already has a virtual account assignedCUSTOMER_NOT_FOUNDNo customer found with the given IDNOT_FOUNDThe requested resource does not existFORBIDDENYour API key lacks permission for this actionDB_ERRORA database error occurred. Retry the request.METHOD_NOT_ALLOWEDThe HTTP method is not supported for this endpointACCOUNT_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 occurredPAYSTACK_ERRORA Paystack provider error occurredLENCO_RESOLVE_FAILEDLenco could not verify this accountRESOLUTION_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 digitsTIMEOUTThe provider request timed out. Retry later.NETWORK_ERRORNetwork error communicating with the provider. Retry later.KYC_REQUIREDCustomer must complete KYC before this actionACCOUNT_FROZENThe virtual account has been frozenINVALID_BANK_CODEThe bank code provided is not recognizedAdditional 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-billsPurchase 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
Payment Links
payment-linkGenerate 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
POS Terminals
lenco-posManage 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
Subscription Payments
subscription-paymentSet 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
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.