BursaPay Developer Gateway
Everything you need to accept payments, send payouts, and build financial products in Nigeria and beyond through one clean REST API.
Get Your API KeysWhat 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.
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.
Portal Navigation
| Tab | Purpose |
|---|---|
| Dashboard | KPI cards, revenue charts, live event feed |
| API Integration | Copy your keys, quick-start code |
| API Keys | Generate, rotate, scope, and revoke keys |
| Customers | Browse and export your customer list |
| Payments | Transaction history with search and filters |
| Wallet | Balance overview and full ledger |
| Withdrawals | Request and track bank payouts |
| Analytics | Charts for volume, top customers, channel breakdown |
| Reports | Export CSV / Excel / PDF |
| Webhooks | Configure endpoints, view delivery logs, retry failures |
| Sandbox | Live API playground proxied via your test key |
| Settings | Checkout branding, settlement schedule, IP allowlist, 2FA |
| Team | Invite 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:
- Step 1 (Hold): Call
POST /api/v1/payment-intents/with"capture_method": "manual". Store the returnedPI-...reference. - Step 2 (Fulfill & Capture): When the order is shipped/fulfilled, call
POST /api/v1/payment-intents/PI-.../capture/to capture funds into your wallet. - 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
| Key | Prefix | Use |
|---|---|---|
| Publishable Test | pk_test_ | Client-side checkout initialisation |
| Secret Test | sk_test_ | Server-side API calls in test mode |
| Publishable Live | pk_live_ | Client-side checkout in production |
| Secret Live | sk_live_ | Server-side API calls in production |
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
| Feature | Test Mode | Live Mode |
|---|---|---|
| No-code setup | Immediate | Requires KYC |
| Real money | No | Yes |
| Webhooks delivered | Yes | Yes |
| Transfers / payouts | Blocked | Enabled |
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
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
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']}")
Other Payment Endpoints
Refunds
Full or partial refunds. Omit amount for a full refund. Wallet balance must cover the refund amount.
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",
}
)
Customers
Customers are created automatically when a payment is initialized. You can also create them explicitly and query their full payment history.
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.
available_balance to reserved_balance on initiation and are debited from reserved only when Paystack confirms completion.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.
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.
Payment Links No-Code Checkout
Create a shareable URL. Customers pay through a hosted BursaPay checkout page no backend code needed on your end.
python
resp = requests.post(
"https://api.bursapay.com/api/v1/payment-links/",
headers={"Authorization": "Bearer sk_test_xxxx"},
json={
"title": "Premium Plan",
"amount": 15000.00,
"currency": "NGN",
"max_uses": 100, # optional cap
"expires_at": "2026-12-31T23:59:59Z", # optional
"success_url": "https://yourapp.com/thanks", # where to send customer after payment
"cancel_url": "https://yourapp.com/checkout", # if customer cancels
}
)
link = resp.json()["data"]
print(link["checkout_url"]) # share this anywhere
| Field | Description |
|---|---|
amount | Fixed amount; omit to let the customer choose |
max_uses | Usage cap; omit for unlimited |
expires_at | Link becomes unusable after this datetime |
success_url | Redirect after successful payment |
cancel_url | Redirect if customer cancels |
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.
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.
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: draft → sent → paid / 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"},
)
Multi-Currency Support
| Code | Currency | Symbol |
|---|---|---|
NGN | Nigerian Naira | ₦ (default) |
USD | US Dollar | $ |
GBP | British Pound | £ |
KES | Kenyan Shilling | KSh |
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
}
)
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.
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 type | When created |
|---|---|
credit | Payment proceeds received |
debit | Refund, withdrawal, or transfer |
fee | Platform fee or provider fee |
settlement | Funds settled to bank |
virtual_account_credit | Dedicated virtual account deposit |
Settlements
Request withdrawals from your wallet to your registered bank account. A BursaPay admin or financial secretary approves, then Paystack initiates the bank transfer.
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
| Attempt | Delay after failure |
|---|---|
| 1 | 60 seconds |
| 2 | 3 minutes |
| 3 | 7 minutes |
| 4 | 15 minutes |
| 5 | 25 minutes |
Webhook Management Endpoints
Disputes
When a customer files a chargeback, BursaPay creates a dispute record and notifies you. Submit evidence before the deadline to contest it.
| Status | Meaning |
|---|---|
open | New evidence can be submitted |
under_review | Awaiting decision |
won | Resolved in your favour |
lost | Funds 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).
| Scope | Grants access to |
|---|---|
payments:read | GET payment endpoints |
payments:write | POST initialize, charge, bulk, schedule cancel |
transfers:write | POST transfer and bulk transfer |
webhooks:manage | Create / update / delete / test webhooks |
customers:read | GET customer endpoints |
customers:write | POST customer create |
invoices:read | GET invoice endpoints |
invoices:write | POST / 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.
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."]}}
| HTTP | Code | Meaning |
|---|---|---|
| 400 | validation_error | Request body failed validation; see errors |
| 400 | invalid_currency | Currency code not supported |
| 400 | invalid_charge_at | Scheduled time in the past or < 60s away |
| 400 | split_shares_exceed_total | Sum of split shares > 1.0 |
| 401 | authentication_failed | API key missing, invalid, or revoked |
| 403 | insufficient_scope | Key lacks the required scope |
| 403 | ip_not_allowed | Client IP not in allowlist |
| 404 | payment_not_found | Reference does not belong to your account |
| 409 | virtual_account_exists | Customer already has an active virtual account |
| 409 | schedule_already_triggered | Scheduled payment cannot be cancelled |
| 422 | insufficient_wallet_balance | Wallet balance too low for refund or withdrawal |
| 429 | rate_limit_exceeded | Too many requests; see Retry-After header |
| 504 | gateway_timeout | Payment provider timed out; safe to retry with same key |
| 500 | internal_error | Unexpected 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.
Python bursapay-sdk
Works with Python 3.8 – 3.12. Uses httpx and supports both sync and async usage.
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",
)
| Resource | Methods |
|---|---|
bp.payments | initialize, verify, retrieve, list, charge, cancel_schedule, bulk_initialize, bulk_status |
bp.customers | create, list, retrieve, update, delete, payments |
bp.transfers | initiate, retrieve, list, bulk |
bp.wallets | balance, ledger, settlements, settlement |
bp.webhooks | create, list, retrieve, update, delete, logs, log_detail, retry, events, send_test |
bp.virtual_accounts | create, list, retrieve |
bp.subscriptions | create_plan, list_plans, enroll, list, retrieve, pause, resume, cancel |
bp.invoices | create, list, retrieve, update, delete |
bp.payment_links | create, list, retrieve, update, delete, analytics |
bp.refunds | create, retrieve |
JavaScript / TypeScript bursapay-sdk
Works in Node.js 18+ (native fetch). Full TypeScript types included. Ships dual CJS + ESM builds.
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.
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
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');
},
});
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)
PYTHONMinimal 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.JSExpress server-side init + BursaPayInline.checkout() popup.
APIs Used
- Initialize Payment
- Verify Payment
- BursaPayInline
E-Commerce Store (Next.js)
NEXT.JSFull-stack Next.js 14 App Router cart + checkout with API routes.
APIs Used
- Initialize Payment
- Verify Payment
SaaS Subscriptions (Python)
PYTHONPlans, customer enrollment, and subscription lifecycle webhooks.
APIs Used
- Subscriptions
- Payments
- Webhooks
Invoicing (Node.js)
NODE.JSCreate a customer, build an invoice, send it, handle invoice.paid webhook.
APIs Used
- Customers
- Invoices
- Webhooks
Virtual Wallet (FastAPI)
FASTAPIProvision a NUBAN virtual account and handle credit webhooks.
APIs Used
- Customers
- Virtual Accounts
- Webhooks
Payment Links (PHP)
PHPCreate single and bulk payment links, fetch analytics.
APIs Used
- Payment Links
Vendor Payouts (Go)
GOInitialize a payment collection then dispatch bulk bank transfers.
APIs Used
- Payments
- Bulk Transfers
- Webhooks
Webhook Server (Node.js)
NODE.JSStandalone webhook receiver with in-memory event dashboard on port 4000.
APIs Used
- Webhooks (all events)
Refunds & Disputes (Python)
PYTHONPartial + 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.
npx bursapay-cli <command>. No setup or code cloning required. Works on Mac, Windows, and Linux (Node.js 16+).Saves your secret API key locally to ~/.bursapay/config.json so you don't need --api-key every time.
Tunnels live events from your account to your local machine with automatic HMAC signatures.
Exercises full backend signature calculation & dispatches test event to local handler.
Staggered re-forwarding of saved JSONL log files back to local server with custom delays.
Inspects & tests webhook HMAC SHA-256 signature verification and timestamp freshness locally.
Generates sandbox test card numbers (Visa, Verve, Mastercard), virtual accounts, USSD codes & customers.
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 AccessQuestions? supports@bursapay.com · Interactive API Docs