Docs API Get API Keys

BursaPay Developer Gateway

Everything you need to accept payments, send payouts, and build financial products in Nigeria and beyond through one clean REST API.

REST API Webhooks NGN · USD · GBP · KES Subscriptions Virtual Accounts Invoicing Split Payments Bulk Transfers
Get Your API Keys

What is the BursaPay Developer Gateway?

The BursaPay Developer Gateway is a full-stack payment integration platform sitting between your application and the underlying payment provider. Instead of dealing with the payment provider directly, you get a single clean API that handles everything.

No Paystack references or keys ever reach your code. BursaPay maps all internal provider identifiers to your own references, keeping your integration clean and portable.

What you get

  • A single REST API at https://api.bursapay.com/api/v1
  • Automatic fee accounting platform fees calculated and ledgered transparently
  • Rich developer portal manage keys, monitor payments, analytics, webhooks, team members
  • Security by default keys hashed, webhook secrets encrypted, all requests logged, risk engine included
  • Subscriptions, invoicing, virtual accounts, payment links, scheduled payments, split payments, bulk transfers all built in

Architecture

text
Your App  ──────────►  BursaPay REST API  (/api/v1/)
                             │
                    ┌────────┴─────────┐
                  gateway_api        gateway
                  (API layer)    (portal + services)
                             │
                    gateway_verification
                       (KYC wizard)
                             │
                         Paystack
                   (payment provider transparent)

Getting Started

Developer accounts are provisioned by a BursaPay Admin. Once your account is created you receive a welcome email with login credentials and a verification link.

ℹ️
You can start using Test Mode immediately after verifying your email no KYC required. Live Mode requires completed verification.

Portal Navigation

TabPurpose
DashboardKPI cards, revenue charts, live event feed
API IntegrationCopy your keys, quick-start code
API KeysGenerate, rotate, scope, and revoke keys
CustomersBrowse and export your customer list
PaymentsTransaction history with search and filters
WalletBalance overview and full ledger
WithdrawalsRequest and track bank payouts
AnalyticsCharts for volume, top customers, channel breakdown
ReportsExport CSV / Excel / PDF
WebhooksConfigure endpoints, view delivery logs, retry failures
SandboxLive API playground proxied via your test key
SettingsCheckout branding, settlement schedule, IP allowlist, 2FA
TeamInvite members, assign roles

Two-Step Payment Authorization & Delayed Capture

For e-commerce, pre-orders, and booking platforms, you can place a 7-day authorization hold on a customer's card when an order is placed and capture the funds later when the order is fulfilled.

Workflow Steps:

  1. Step 1 (Hold): Call POST /api/v1/payment-intents/ with "capture_method": "manual". Store the returned PI-... reference.
  2. Step 2 (Fulfill & Capture): When the order is shipped/fulfilled, call POST /api/v1/payment-intents/PI-.../capture/ to capture funds into your wallet.
  3. Step 3 (Or Cancel): If the customer cancels before shipping, call POST /api/v1/payment-intents/PI-.../cancel/ to release the hold with zero fees.
// 1. Create Payment Intent (Hold Funds)
const intentRes = await fetch('https://api.bursapay.com/api/v1/payment-intents/', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_test_xxxx',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    amount: 15000,
    email: 'buyer@example.com',
    capture_method: 'manual',
    metadata: { order_id: 'ORD-9918' }
  })
});
const intent = await intentRes.json();
console.log('Intent status:', intent.data.status); // requires_capture

// 2. Capture Funds Upon Order Fulfillment
const captureRes = await fetch(`https://api.bursapay.com/api/v1/payment-intents/${intent.data.intent_reference}/capture/`, {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_test_xxxx',
    'Content-Type': 'application/json'
  }
});
const captured = await captureRes.json();
console.log('Captured status:', captured.data.status); // succeeded

Framework Quickstart Cards

Integrate BursaPay into your backend stack in seconds. Select your framework below to copy the quickstart checkout code:

<!-- 1. Include BursaPay Inline JS SDK -->
<script src="https://bursapay.com/v1/bursapay.js"></script>

<!-- 2. Open inline popup on click -->
<script>
function payWithBursaPay() {
  var popup = BursaPayPop.setup({
    key: 'pk_live_your_publishable_key', // or pk_test_...
    email: 'customer@example.com',
    amount: 500000, // Amount in kobo / cents (₦5,000.00)
    currency: 'NGN',
    ref: 'BP-' + Math.floor(Math.random() * 1e9 + 1),
    onSuccess: function(response) {
      console.log('Payment successful! Reference:', response.reference);
      // Redirect or update UI
    },
    onClose: function() {
      console.log('Customer closed payment popup modal');
    }
  });

  popup.openIframe();
}
</script>

<button onclick="payWithBursaPay()">Pay Now with BursaPay</button>

Authentication & Security

API Key Types

KeyPrefixUse
Publishable Testpk_test_Client-side checkout initialisation
Secret Testsk_test_Server-side API calls in test mode
Publishable Livepk_live_Client-side checkout in production
Secret Livesk_live_Server-side API calls in production
⚠️
Secret keys are shown exactly once at generation time, then stored as a SHA-256 hash. Copy them to your secrets manager immediately.

Sending the Key

http
Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxx

Two-Factor Authentication (2FA)

TOTP-based 2FA (Google Authenticator / Authy) is required for switching to Live Mode, rotating or revealing live keys, and initiating large withdrawals.

Test vs Live Mode

FeatureTest ModeLive Mode
No-code setupImmediateRequires KYC
Real moneyNoYes
Webhooks deliveredYesYes
Transfers / payoutsBlockedEnabled

Test Mode is completely isolated. Test payments and wallet entries never appear in Live Mode and vice versa.

Payments

The payment lifecycle: initialize → customer pays on hosted page → verify → webhook fired.

Initialize a Payment

POST /api/v1/payments/initialize/
python
import requests

response = requests.post(
    "https://api.bursapay.com/api/v1/payments/initialize/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={
        "amount": 5000.00,
        "email": "customer@example.com",
        "currency": "NGN",                        # NGN, USD, GBP, KES
        "callback_url": "https://yourapp.com/pay/callback",
        "metadata": {"order_id": "ORD-001"},
        "idempotency_key": "order-ORD-001-v1",    # safe to retry
    }
)
data = response.json()["data"]
# Redirect customer to data["authorization_url"]
print(data["reference"])         # BPAY-xxxxxxxxxx
print(data["authorization_url"]) # https://checkout.paystack.com/...

Verify a Payment

POST /api/v1/payments/verify/
python
response = requests.post(
    "https://api.bursapay.com/api/v1/payments/verify/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={"reference": "BPAY-xxxxxxxxxx"}
)
payment = response.json()["data"]
if payment["status"] == "success":
    print(f"₦{payment['amount']} received. Net: ₦{payment['net_amount']}")
💡
On a successful verify BursaPay automatically: updates payment status, credits your wallet via a ledger entry, and dispatches a payment.success webhook to all subscribed endpoints.

Other Payment Endpoints

GET /api/v1/payments/ list (cursor-paginated)
GET /api/v1/payments/{reference}/ single payment
POST /api/v1/payments/charge/ charge a saved authorization (repeat charge)
POST /api/v1/payments/bulk/ bulk initialize (payroll, batch invoices)
GET /api/v1/payments/bulk/{batch_ref}/ check bulk job status
DELETE /api/v1/payments/{reference}/schedule/ cancel a scheduled payment

Refunds

Full or partial refunds. Omit amount for a full refund. Wallet balance must cover the refund amount.

POST /api/v1/refunds/
python
requests.post(
    "https://api.bursapay.com/api/v1/refunds/",
    headers={
        "Authorization": "Bearer sk_test_xxxx",
        "Idempotency-Key": "refund-ORD-001",   # safe to retry
    },
    json={
        "payment_reference": "BPAY-xxxxxxxxxx",
        "amount": 2500.00,                     # omit for full refund
        "reason": "Customer changed mind",
    }
)
GET /api/v1/refunds/{refund_ref}/ get refund detail

Customers

Customers are created automatically when a payment is initialized. You can also create them explicitly and query their full payment history.

POST /api/v1/customers/ create
GET /api/v1/customers/ list (cursor-paginated)
GET /api/v1/customers/{ref}/ detail
GET /api/v1/customers/{ref}/payments/ payment history (filterable by status)
python
# Create a customer explicitly
requests.post(
    "https://api.bursapay.com/api/v1/customers/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={"email": "ada@example.com", "name": "Ada Okafor", "phone": "+2348012345678"}
)

Transfers / Payouts

Push money from your BursaPay wallet to any Nigerian bank account. Live Mode only.

ℹ️
Funds move from available_balance to reserved_balance on initiation and are debited from reserved only when Paystack confirms completion.
POST /api/v1/transfers/ initiate
POST /api/v1/transfers/bulk/ bulk (payroll, mass payouts)
GET /api/v1/transfers/ list
GET /api/v1/transfers/{reference}/ detail
python
requests.post(
    "https://api.bursapay.com/api/v1/transfers/",
    headers={
        "Authorization": "Bearer sk_live_xxxx",
        "Idempotency-Key": "payout-vendor-aug-03",
    },
    json={
        "amount": 25000.00,
        "bank_code": "058",          # GTBank
        "account_number": "0123456789",
        "account_name": "Emeka Eze",
        "narration": "Vendor settlement August",
    }
)

Webhooks fired: transfer.success · transfer.failed

Virtual Accounts (Dedicated Nuban)

Give each customer a unique bank account number. Money sent to that account is automatically credited to your BursaPay wallet.

POST /api/v1/virtual-accounts/ create (1 per customer)
GET /api/v1/virtual-accounts/ list
GET /api/v1/virtual-accounts/{id}/ detail
python
resp = requests.post(
    "https://api.bursapay.com/api/v1/virtual-accounts/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={"customer_reference": "CUST-xyz789"}
)
account = resp.json()["data"]
print(account["account_number"])  # Give this to the customer to send money to
print(account["bank_name"])       # e.g. Wema Bank

When a customer sends money: wallet is credited, virtual_account.credited webhook fires.

Subscriptions & Recurring Billing

Define billing plans and enroll customers for automatic recurring charges. BursaPay handles the billing cycle automatically via a background task every 5 minutes.

POST /api/v1/subscriptions/plans/ create plan
POST /api/v1/subscriptions/ enroll customer
POST /api/v1/subscriptions/{id}/pause/
POST /api/v1/subscriptions/{id}/cancel/
python
# 1. Create a plan
plan = requests.post(
    "https://api.bursapay.com/api/v1/subscriptions/plans/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={"name": "Pro Monthly", "amount": 9500.00, "currency": "NGN",
          "interval": "monthly", "trial_period_days": 14}
).json()["data"]

# 2. Enroll a customer (they must have a saved authorization_code from a prior payment)
requests.post(
    "https://api.bursapay.com/api/v1/subscriptions/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={"customer_reference": "CUST-xyz789",
          "plan_id": plan["id"],
          "authorization_code": "AUTH_xxxxxxxxxx"}
)

Intervals: daily · weekly · monthly · yearly

Webhooks: subscription.charged · subscription.charge_failed · subscription.cancelled

Invoicing

Create structured, itemised payment requests. BursaPay auto-generates a payment link for each invoice so the customer can pay online. Supports partial payments and overdue tracking.

POST /api/v1/invoices/ create
PATCH /api/v1/invoices/{reference}/ update / send
GET /api/v1/invoices/ list
python
# Create invoice total_amount computed automatically from line items
resp = requests.post(
    "https://api.bursapay.com/api/v1/invoices/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={
        "customer_reference": "CUST-xyz789",
        "due_date": "2026-08-31",
        "currency": "NGN",
        "line_items": [
            {"description": "Logo Design", "quantity": 1, "unit_price": 50000.00},
            {"description": "Brand Guide (pages)", "quantity": 10, "unit_price": 2000.00},
        ]
    }
)
invoice = resp.json()["data"]
print(invoice["total_amount"])   # 70000.00 auto-computed

# Send the invoice (generates payment link)
requests.patch(
    f"https://api.bursapay.com/api/v1/invoices/{invoice['reference']}/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={"action": "send"}
)

Statuses: draftsentpaid / partially_paid / overdue / cancelled

Webhooks: invoice.paid · invoice.partially_paid · invoice.overdue

Payment Scheduling

Schedule a payment to be charged automatically at a future datetime. Useful for deferred payments, trial-to-paid conversions, and pre-authorisations.

python
# charge_at must be > 60 seconds in the future
requests.post(
    "https://api.bursapay.com/api/v1/payments/initialize/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={
        "amount": 12000.00,
        "email": "user@example.com",
        "authorization_code": "AUTH_xxxxxxxxxx",
        "charge_at": "2026-08-10T09:00:00Z",  # future UTC datetime
    }
)

# Cancel before it fires
requests.delete(
    "https://api.bursapay.com/api/v1/payments/BPAY-xxxxxxxxxx/schedule/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
)
ℹ️
A Celery beat task runs every minute, picks up due scheduled payments, and charges them via the stored authorization code. On success the wallet is credited and a payment.success webhook fires.

Multi-Currency Support

CodeCurrencySymbol
NGNNigerian Naira₦ (default)
USDUS Dollar$
GBPBritish Pound£
KESKenyan ShillingKSh

Pass currency in the payment initialize body. All amounts are in the major unit (not kobo/cents). Passing an unsupported currency returns HTTP 400 invalid_currency.

Split Payments

Automatically divide payment proceeds among multiple recipients at initialization time. Perfect for marketplaces and platform commissions.

python
requests.post(
    "https://api.bursapay.com/api/v1/payments/initialize/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={
        "amount": 100000.00,
        "email": "buyer@example.com",
        "splits": [
            {"subaccount": "ACCT_vendor_a_code", "share": 0.60},  # 60%
            {"subaccount": "ACCT_platform_fee",  "share": 0.10},  # 10%
        ],
        # Remaining 30% stays in your BursaPay wallet
    }
)
⚠️
The sum of all share values must be ≤ 1.0. Exceeding this returns split_shares_exceed_total.

Wallet & Ledger

Your wallet accumulates net payment proceeds. All balance changes go through an immutable ledger the balance is never edited directly.

GET /api/v1/wallet/balance
GET /api/v1/wallet/ledger/ full transaction history
json
// GET /api/v1/wallet/balance response
{
  "data": {
    "available_balance": 245000.00,
    "pending_balance":    12000.00,
    "reserved_balance":   25000.00,
    "total_balance":     282000.00,
    "currency": "NGN"
  }
}
Entry typeWhen created
creditPayment proceeds received
debitRefund, withdrawal, or transfer
feePlatform fee or provider fee
settlementFunds settled to bank
virtual_account_creditDedicated virtual account deposit
ℹ️
Platform fee default: 10 NGN per successful transaction. Admins can set a custom per-developer override (including zero-fee).

Settlements

Request withdrawals from your wallet to your registered bank account. A BursaPay admin or financial secretary approves, then Paystack initiates the bank transfer.

GET /api/v1/wallet/settlements/
GET /api/v1/wallet/settlements/{reference}/

Configure auto-settlement frequency from Settings → Settlement Schedule: daily, weekly, or manual.

Webhooks

BursaPay delivers real-time event notifications to your HTTPS endpoint. Every payload is signed with HMAC SHA-256.

Configure an Endpoint

python
requests.post(
    "https://api.bursapay.com/api/v1/webhooks/",
    headers={"Authorization": "Bearer sk_test_xxxx"},
    json={
        "url": "https://yourapp.com/webhooks/bursapay",
        "events": ["payment.success", "refund.completed", "transfer.success"],
        "webhook_version": "v2",   # "v1" (flat) or "v2" (structured envelope)
    }
)

Verify the Signature (Python)

python
import hmac, hashlib, json, time

def verify_signature(raw_body: bytes, sig_header: str, secret: str) -> bool:
    parts = dict(item.split("=", 1) for item in sig_header.split(","))
    timestamp = parts.get("t", "")
    received  = parts.get("v1", "")

    if abs(time.time() - int(timestamp)) > 300:   # reject stale (>5 min)
        return False

    payload = json.loads(raw_body)
    message = f"{timestamp}.{json.dumps(payload, sort_keys=True, separators=(',', ':'))}"
    expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received)

# In your Django/Flask view:
sig_header = request.headers.get("X-BursaPay-Signature", "")
if not verify_signature(request.body, sig_header, WEBHOOK_SECRET):
    return HttpResponse(status=403)

Verify the Signature (Node.js)

javascript
const crypto = require('crypto');

function verifySignature(rawBody, sigHeader, secret) {
  const parts = Object.fromEntries(sigHeader.split(',').map(p => p.split('=', 2)));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;

  const message = `${parts.t}.${rawBody}`;
  const expected = crypto.createHmac('sha256', secret).update(message).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

// Express:
app.post('/webhooks/bursapay', express.raw({type:'application/json'}), (req, res) => {
  if (!verifySignature(req.body, req.headers['x-bursapay-signature'], WEBHOOK_SECRET))
    return res.sendStatus(403);
  const event = JSON.parse(req.body);
  if (event.event === 'payment.success') { /* handle */ }
  res.sendStatus(200);
});

Retry Policy

AttemptDelay after failure
160 seconds
23 minutes
37 minutes
415 minutes
525 minutes

Webhook Management Endpoints

POST /api/v1/webhooks/test/ send a test delivery
GET /api/v1/webhooks/{id}/logs/ delivery history
POST /api/v1/webhooks/logs/{log_id}/retry/ manual retry

Disputes

When a customer files a chargeback, BursaPay creates a dispute record and notifies you. Submit evidence before the deadline to contest it.

GET /api/v1/disputes/
GET /api/v1/disputes/{reference}/
PATCH /api/v1/disputes/{reference}/ submit evidence
StatusMeaning
openNew evidence can be submitted
under_reviewAwaiting decision
wonResolved in your favour
lostFunds returned to customer

Webhooks: dispute.created · dispute.won · dispute.lost

API Key Scopes

Restrict what a key can do by assigning scopes at creation time. An empty scopes list grants full access (backward compatible).

ScopeGrants access to
payments:readGET payment endpoints
payments:writePOST initialize, charge, bulk, schedule cancel
transfers:writePOST transfer and bulk transfer
webhooks:manageCreate / update / delete / test webhooks
customers:readGET customer endpoints
customers:writePOST customer create
invoices:readGET invoice endpoints
invoices:writePOST / PATCH / DELETE invoice

A scoped key attempting a forbidden action receives HTTP 403 insufficient_scope.

IP Allowlisting

Restrict which IP addresses can use your API keys. Supports individual IPs and CIDR ranges (e.g. 10.0.0.0/24). Managed from Settings → IP Allowlist in the portal.

An empty allowlist permits all IPs. Blocked requests receive HTTP 403 ip_not_allowed.

Rate Limiting

Applied at two levels simultaneously: per API key and per IP address. When exceeded:

json
// HTTP 429 Too Many Requests
{"success": false, "code": "rate_limit_exceeded", "retry_after": 30}

Best practices: use idempotency keys for safe retries; implement exponential backoff; use bulk endpoints instead of looping single-item calls.

Idempotency

Pass Idempotency-Key header on any mutating request. Re-submitting the same key returns the original response without re-processing.

http
POST /api/v1/payments/initialize/
Authorization: Bearer sk_test_xxxx
Idempotency-Key: order-ORD-001-attempt-3

Both Idempotency-Key and X-Idempotency-Key headers are accepted. Keys are scoped per developer per endpoint.

API Logging & Audit Trail

Every API request is logged asynchronously. Each entry stores: timestamp, duration (ms), developer, API key, HTTP method, endpoint, status code, request/response bodies, client IP, user agent, and a unique X-Request-Id.

🔒
Sensitive fields (card_number, cvv, pin, password, authorization_code, account_number, secret, api_key, and more) are automatically redacted to [REDACTED] before storage.

Logs are retained for 90 days (auto-purged by nightly Celery task). The History tab in the portal shows your full activity audit log with CSV export.

Analytics & Reports

The portal dashboard provides live-computed KPIs including wallet balance, today's revenue, monthly revenue, success rate, conversion rate, failure rate, refund rate, top customers, channel breakdown, and peak transaction hours. Charts support daily / weekly / monthly / yearly ranges.

From the Reports tab: export CSV, Excel, or PDF for revenue, transactions, customers, wallet, and withdrawals. Large exports are queued to Celery and emailed when ready. Files auto-delete after 7 days.

Error Reference

All errors follow a consistent envelope:

json
{"success": false, "code": "error_code_here",
 "message": "Human-readable description.",
 "errors": {"field_name": ["Specific error."]}}
HTTPCodeMeaning
400validation_errorRequest body failed validation; see errors
400invalid_currencyCurrency code not supported
400invalid_charge_atScheduled time in the past or < 60s away
400split_shares_exceed_totalSum of split shares > 1.0
401authentication_failedAPI key missing, invalid, or revoked
403insufficient_scopeKey lacks the required scope
403ip_not_allowedClient IP not in allowlist
404payment_not_foundReference does not belong to your account
409virtual_account_existsCustomer already has an active virtual account
409schedule_already_triggeredScheduled payment cannot be cancelled
422insufficient_wallet_balanceWallet balance too low for refund or withdrawal
429rate_limit_exceededToo many requests; see Retry-After header
504gateway_timeoutPayment provider timed out; safe to retry with same key
500internal_errorUnexpected server error; share X-Request-Id with support

Webhook Event Catalog

Subscribe to any combination of these events when configuring a webhook endpoint.

Payments

payment.success  payment.failed

Refunds

refund.completed

Transfers

transfer.success  transfer.failed

Withdrawals

withdrawal.completed

Customers

customer.created

Virtual Accounts

virtual_account.credited

Subscriptions

subscription.charged  subscription.charge_failed  subscription.cancelled  subscription.paused

Invoices

invoice.paid  invoice.partially_paid  invoice.overdue

Disputes

dispute.created  dispute.won  dispute.lost

Batch

batch.completed

Official SDKs

Skip the boilerplate. The official BursaPay SDKs wrap every API endpoint with typed methods, automatic error mapping, and built-in webhook signature verification.

📦
Official SDK packages are published for Python, JavaScript / TypeScript, PHP, and Go covering the full API surface payments, transfers, customers, webhooks, virtual accounts, subscriptions, invoices, payment links, wallets, and refunds.

Python bursapay-sdk

Works with Python 3.8 – 3.12. Uses httpx and supports both sync and async usage.

PyPI pip install bursapay-sdk
python
from bursapay import BursaPay

bp = BursaPay("sk_test_xxxx")                          # sandbox
bp = BursaPay("sk_live_xxxx")                          # production
bp = BursaPay("sk_test_xxxx",
              base_url="http://localhost:8000/api/v1") # local dev

# Initialize a payment
payment = bp.payments.initialize(
    amount=5000,
    email="customer@example.com",
    currency="NGN",
    metadata={"order_id": "ORD-123"},
)
redirect_url = payment["authorization_url"]

# Verify after callback
result = bp.payments.verify("BP-XXXX")
print(result["status"])   # "success"

# Async support
async with bp.async_client() as abp:
    payment = await abp.payments.initialize(amount=5000, email="a@b.com")

# Verify a webhook signature
from bursapay import BursaPay
is_valid = BursaPay.verify_webhook_signature(
    request.body,
    request.headers.get("X-BursaPay-Signature", ""),
    "your_webhook_secret",
)
ResourceMethods
bp.paymentsinitialize, verify, retrieve, list, charge, cancel_schedule, bulk_initialize, bulk_status
bp.customerscreate, list, retrieve, update, delete, payments
bp.transfersinitiate, retrieve, list, bulk
bp.walletsbalance, ledger, settlements, settlement
bp.webhookscreate, list, retrieve, update, delete, logs, log_detail, retry, events, send_test
bp.virtual_accountscreate, list, retrieve
bp.subscriptionscreate_plan, list_plans, enroll, list, retrieve, pause, resume, cancel
bp.invoicescreate, list, retrieve, update, delete
bp.payment_linkscreate, list, retrieve, update, delete, analytics
bp.refundscreate, retrieve

JavaScript / TypeScript bursapay-sdk

Works in Node.js 18+ (native fetch). Full TypeScript types included. Ships dual CJS + ESM builds.

npm npm install bursapay-sdk
typescript
import { BursaPay } from 'bursapay-sdk';

const bp = new BursaPay('sk_test_xxxx');

// Initialize a payment
const payment = await bp.payments.initialize({
  amount: 5000,
  email: 'customer@example.com',
  currency: 'NGN',
});
// Redirect customer to payment.authorization_url

// Verify
const result = await bp.payments.verify('BP-XXXX');
console.log(result.status); // "success"

// Webhook signature verification (Express)
app.post('/hooks/bursapay', express.raw({ type: '*/*' }), (req, res) => {
  const ok = BursaPay.verifyWebhookSignature(
    req.body,
    req.headers['x-bursapay-signature'],
    process.env.BURSAPAY_WEBHOOK_SECRET,
  );
  if (!ok) return res.sendStatus(401);
  const event = JSON.parse(req.body.toString());
  // handle event...
  res.sendStatus(200);
});

PHP bursapay/bursapay-php

Works with PHP 8.0+. PSR-4 compatible, relies on Guzzle, and includes typed exception handling and HMAC verification.

Composer composer require bursapay/bursapay-php
php
use BursaPay\BursaPay;
use BursaPay\Resources\Webhooks;
use BursaPay\Exceptions\BursaPayException;

$bp = new BursaPay('sk_test_xxxx');

// Initialize payment
$response = $bp->payments->initialize([
    'amount' => 5000,
    'email' => 'customer@example.com',
    'currency' => 'NGN',
]);

// Verify payment
$res = $bp->payments->verify('BP-XXXX');

// Verify webhook signature
$event = Webhooks::constructEvent(
    $rawBody,
    $_SERVER['HTTP_X_BURSAPAY_SIGNATURE'],
    $_SERVER['HTTP_X_BURSAPAY_TIMESTAMP'],
    $secret
);

Go github.com/bursapay/bursapay-go

Works with Go 1.18+. Idiomatic client using standard net/http, context parameters, and typed response structs.

go get go get github.com/bursapay/bursapay-go
go
import (
    "context"
    "github.com/bursapay/bursapay-go"
)

client := bursapay.NewClient("sk_test_xxxx")

// Initialize payment
payment, err := client.Payments.Initialize(context.Background(), &bursapay.PaymentInitRequest{
    Amount:   5000,
    Email:    "customer@example.com",
    Currency: "NGN",
})

// Verify Webhook Signature
event, err := bursapay.ConstructEvent(body, sigHeader, tsHeader, secret)

Browser Inline Checkout

Open your hosted BursaPay checkout as a modal popup no redirect needed. Customers stay on your page.

typescript
import { BursaPayInline } from 'bursapay-sdk';

BursaPayInline.checkout({
  publicKey: 'pk_live_xxxx',
  reference: 'BP-XXXX',          // generated server-side
  onSuccess: (ref) => {
    window.location.href = `/order/success?ref=${ref}`;
  },
  onClose: () => {
    console.log('Customer closed checkout');
  },
});
ℹ️
The reference must be pre-generated on your server via POST /api/v1/payments/initialize/ before calling BursaPayInline.checkout(). Never initialize payments from the browser your secret key would be exposed.

Error Handling

Both SDKs map HTTP errors to typed exceptions so you can catch exactly what you need:

python
from bursapay.exceptions import (
    AuthenticationError, ValidationError, NotFoundError,
    RateLimitError, ServerError,
)

try:
    bp.payments.initialize(amount=5000, email="x@y.com", currency="XYZ")
except ValidationError as e:
    print(e.error_code)     # "invalid_currency"
    print(e.field_errors)   # field-level dict
except AuthenticationError:
    print("Check your API key")
    

Explore Integration Demos

Real, runnable code across Python, Node.js, Next.js, Go, and PHP. Each demo ships in two variants: one using the official SDK and one using raw HTTP.

View all on GitHub →

Simple Checkout (Python)

PYTHON

Minimal Flask app: initialize a payment and verify it in under 50 lines.

APIs Used

  • Initialize Payment
  • Verify Payment

Simple Checkout + Inline Popup (Node.js)

NODE.JS

Express server-side init + BursaPayInline.checkout() popup.

APIs Used

  • Initialize Payment
  • Verify Payment
  • BursaPayInline

E-Commerce Store (Next.js)

NEXT.JS

Full-stack Next.js 14 App Router cart + checkout with API routes.

APIs Used

  • Initialize Payment
  • Verify Payment

SaaS Subscriptions (Python)

PYTHON

Plans, customer enrollment, and subscription lifecycle webhooks.

APIs Used

  • Subscriptions
  • Payments
  • Webhooks

Invoicing (Node.js)

NODE.JS

Create a customer, build an invoice, send it, handle invoice.paid webhook.

APIs Used

  • Customers
  • Invoices
  • Webhooks

Virtual Wallet (FastAPI)

FASTAPI

Provision a NUBAN virtual account and handle credit webhooks.

APIs Used

  • Customers
  • Virtual Accounts
  • Webhooks

Payment Links (PHP)

PHP

Create single and bulk payment links, fetch analytics.

APIs Used

  • Payment Links

Vendor Payouts (Go)

GO

Initialize a payment collection then dispatch bulk bank transfers.

APIs Used

  • Payments
  • Bulk Transfers
  • Webhooks

Webhook Server (Node.js)

NODE.JS

Standalone webhook receiver with in-memory event dashboard on port 4000.

APIs Used

  • Webhooks (all events)

Refunds & Disputes (Python)

PYTHON

Partial + full refunds, dispute evidence submission, refund webhook.

APIs Used

  • Refunds
  • Disputes
  • Webhooks

BursaPay CLI (bursapay-cli)

Official command-line interface for local webhook debugging, real-time event tunneling, log replay, and terminal event triggering.

🚀
Instant execution via npx bursapay-cli <command>. No setup or code cloning required. Works on Mac, Windows, and Linux (Node.js 16+).
bursapay login npx bursapay-cli login

Saves your secret API key locally to ~/.bursapay/config.json so you don't need --api-key every time.

bursapay listen npx bursapay-cli listen --forward-to http://localhost:8000/webhooks/

Tunnels live events from your account to your local machine with automatic HMAC signatures.

bursapay trigger npx bursapay-cli trigger payment.success --forward-to http://localhost:8000/webhooks/

Exercises full backend signature calculation & dispatches test event to local handler.

bursapay replay npx bursapay-cli replay --log-file ./events.jsonl --delay 200

Staggered re-forwarding of saved JSONL log files back to local server with custom delays.

bursapay verify npx bursapay-cli verify --signature v1=a7e5... --body '{"event":"payment.success"}'

Inspects & tests webhook HMAC SHA-256 signature verification and timestamp freshness locally.

bursapay mock npx bursapay-cli mock card

Generates sandbox test card numbers (Visa, Verve, Mastercard), virtual accounts, USSD codes & customers.

bursapay status npx bursapay-cli status --api-key sk_test_xxxx

Checks API server connectivity, key validity, mode (TEST/LIVE), and stream connection limits.

Complete CLI Flags Reference

Flag Type Description
--forward-to URL Local HTTP URL to forward webhooks to (default: http://localhost:8000/webhooks/).
--api-key string BursaPay secret test key (sk_test_...) or live key. Reads $BURSAPAY_SECRET_KEY.
--env test | live Validates secret key prefix and environment mode.
--filter string Comma-separated list of event types to forward (e.g. payment.success,refund.completed).
--since string Filter events since relative time (e.g. 15m, 1h) or ISO timestamp.
--delay integer Stagger delay in milliseconds between replayed events (default: 100ms).
--log-file path File path to append incoming events in JSON Lines format (.jsonl).

Ready to integrate?

Apply for a developer account and start building in test mode immediately no approval needed for sandbox.

Request API Access

Questions? supports@bursapay.com  ·  Interactive API Docs