Paystack API Integration Reference¶
Source: Paystack Developer Documentation
Base URL:https://api.paystack.co
Auth:Authorization: Bearer sk_live_<secret_key>(all requests)
Updated: March 2026
Treasury payment workflow¶
See payment-workflow.md for the full end-to-end flow (intent creation, shared pay page, initiate_url, gateway redirect, callback page, webhooks). This page covers only the Paystack API specifics: hosted checkout, QR-code pay, transfers/payouts, and the credential model below.
Platform Configuration¶
The Paystack account belongs to Codevertex (platform owner). All tenant payments are collected into the platform Paystack account. Funds are redistributed to tenant accounts via Paystack Transfers.
| Config Level | Who Sets It | Fields |
|---|---|---|
| Platform | Codevertex superadmin | PAYSTACK_SECRET_KEY, PAYSTACK_PUBLIC_KEY, PAYSTACK_WEBHOOK_SECRET |
| Tenant | Tenant admin | Payout account details (recipient_code created via Paystack Transfer Recipients API) |
1. Transactions — Initiate Payment¶
Purpose: Start a hosted checkout payment (card, bank, USSD, mobile money, QR).
Endpoint: POST /transaction/initialize
{
"amount": 10000,
"email": "customer@example.com",
"currency": "KES",
"reference": "ORDER-123-uuid",
"callback_url": "https://app.bengobox.com/payments/verify?ref=ORDER-123",
"metadata": {
"order_id": "uuid",
"tenant_id": "uuid",
"source_service": "ordering"
},
"channels": ["card", "bank_transfer", "mobile_money", "ussd"]
}
Amount: In lowest currency unit (KES cents = kobo equivalent, but KES uses 100 kobos per shilling). For KES 100 → amount: 10000.
Response: Returns authorization_url (redirect customer here) and access_code.
Channels supported for Kenya: card, bank_transfer, mobile_money (M-Pesa via Paystack), ussd.
Kenya Mobile Money Channel (Paystack-proxied M-Pesa)¶
{
"amount": 10000,
"email": "customer@example.com",
"currency": "KES",
"channels": ["mobile_money"],
"mobile_money": {
"phone": "0712345678",
"provider": "mpesa"
}
}
2. Transactions — Verify Payment¶
Purpose: Verify a transaction after customer returns from checkout (or on webhook receipt).
Endpoint: GET /transaction/verify/:reference
{
"status": true,
"data": {
"reference": "ORDER-123-uuid",
"status": "success",
"amount": 10000,
"currency": "KES",
"paid_at": "2026-03-09T12:00:00.000Z",
"gateway_response": "Successful",
"channel": "card",
"fees": 150,
"customer": {"email": "customer@example.com"},
"metadata": {"order_id": "uuid", "tenant_id": "uuid"}
}
}
Statuses: success, failed, abandoned, ongoing, pending.
3. Transactions — List¶
Endpoint: GET /transaction?perPage=50&page=1&status=success&from=2026-01-01&to=2026-03-31&customer=<email>
4. Webhooks¶
Configure webhook URL in Paystack Dashboard → Settings → API Keys & Webhooks.
Endpoint receives: POST /webhooks/paystack
Verify: x-paystack-signature: HMAC-SHA512(request_body, secret_key) — already implemented in treasury-api.
Events:
| Event | Trigger |
|---|---|
| charge.success | Payment completed successfully |
| charge.dispute.create | Customer raised chargeback |
| charge.dispute.resolve | Chargeback resolved |
| transfer.success | B2C transfer (payout) succeeded |
| transfer.failed | Transfer failed |
| transfer.reversed | Transfer reversed after failure |
| invoice.create | Subscription invoice generated |
| invoice.update | Invoice updated |
| invoice.payment_failed | Subscription payment failed |
| subscription.create | New subscription activated |
| subscription.disable | Subscription paused |
| subscription.expiring_cards | Customer card expiring |
| paymentrequest.success | Payment link paid (Paystack Invoice) |
| refund.processed | Refund completed |
5. Refunds¶
Endpoint: POST /refund
{
"transaction": "transaction_reference_or_id",
"amount": 5000,
"currency": "KES",
"customer_note": "Order cancelled by customer",
"merchant_note": "Refund for order #123"
}
Full refund: omit amount. Partial refund: specify amount in lowest denomination.
6. Transfer Recipients — Create¶
Purpose: Register a tenant's bank account or mobile money wallet to receive payouts.
Endpoint: POST /transferrecipient
Bank Account (Kenya)¶
{
"type": "nuban",
"name": "Acme Retail",
"account_number": "0001234567",
"bank_code": "068",
"currency": "KES"
}
M-Pesa (Mobile Money)¶
{
"type": "mobile_money",
"name": "Acme Retail",
"account_number": "0712345678",
"bank_code": "MPESA_KE",
"currency": "KES"
}
Response: Returns recipient_code (e.g., RCP_12abc). Store as payout_config.recipient_code.
7. Transfer Recipients — List Banks¶
Purpose: Get list of banks and mobile money providers for a country.
Endpoint: GET /bank?country=kenya&pay_with_bank_transfer=true&type=mobile_money
Returns array of banks with code and name. Used to populate payout account form dropdowns.
8. Transfers — Initiate Single Transfer (Payout)¶
Purpose: Send collected funds to a tenant's registered account.
Endpoint: POST /transfer
{
"source": "balance",
"amount": 500000,
"recipient": "RCP_abc12",
"reason": "Weekly payout - Week 2026-W10",
"currency": "KES",
"reference": "PAYOUT-tenant-uuid-2026-W10"
}
Note: Transfers in Kenya require OTP approval (unless OTP is disabled in Dashboard). For production automation, enable "Disable OTP" in Paystack Settings.
Response:
{
"transfer_code": "TRF_1234",
"reference": "PAYOUT-tenant-uuid-2026-W10",
"status": "pending"
}
9. Transfers — Initiate Bulk Transfer¶
Purpose: Send payouts to multiple recipients in one API call.
Endpoint: POST /transfer/bulk
{
"currency": "KES",
"source": "balance",
"transfers": [
{
"amount": 150000,
"recipient": "RCP_recipient1",
"reason": "Vendor payout",
"reference": "PAYOUT-recipient1-001"
},
{
"amount": 80000,
"recipient": "RCP_recipient2",
"reason": "Vendor payout",
"reference": "PAYOUT-recipient2-001"
}
]
}
10. Transfers — Verify Transfer¶
Endpoint: GET /transfer/verify/:reference
Returns current status: success, failed, pending, reversed.
11. Settlements — List (Paystack → Platform Bank Account)¶
Purpose: View Paystack auto-settlement payouts to the platform's bank account.
Endpoint: GET /settlement?perPage=50&page=1&from=2026-01-01&to=2026-03-31
Note: Paystack automatically settles collected funds to the platform's nominated bank account on a T+1 or T+2 basis depending on account type.
12. Balance — Check Platform Balance¶
Endpoint: GET /balance
{
"currency": "KES",
"balance": 1250000
}
Used before initiating transfers to ensure sufficient balance.
13. Subscriptions API (Recurring Billing for SaaS Plans)¶
Purpose: Charge tenants recurring subscription fees via card-on-file or direct debit.
Create Plan¶
POST /plan
{
"name": "GROWTH Plan",
"interval": "monthly",
"amount": 499900,
"currency": "KES"
}
Create Subscription¶
POST /subscription
{
"customer": "customer_code",
"plan": "PLN_abc",
"authorization": "AUTH_xyz"
}
Disable/Enable Subscription¶
POST /subscription/disable | POST /subscription/enable
Integration note: subscriptions-api manages the subscription lifecycle. When a tenant pays via Paystack, the authorization token is stored and used for future recurring charges.
14. Payment Pages / Payment Links¶
Purpose: Generate shareable payment links (e.g., for invoices, subscription upgrade prompts).
Endpoint: POST /page
{
"name": "Upgrade to GROWTH Plan",
"description": "Monthly subscription payment",
"amount": 499900,
"currency": "KES",
"metadata": {"tenant_id": "uuid", "plan": "GROWTH"}
}
Returns link (e.g., https://paystack.com/pay/bengobox-growth).
15. QR Code Payments (South Africa only)¶
[!IMPORTANT] Paystack QR code payments (Visa QR) are not available in Kenya as of 2025. For QR payments in Kenya, use M-Pesa QR Code (see
mpesa-integration-reference.md).
16. Dedicated Virtual Accounts (DVA)¶
Purpose: Generate a dedicated bank account number per customer for seamless bank transfer payments.
Availability: Nigeria currently; available in Ghana (June 2025). Not available in Kenya yet.
Transaction Fee Calculation¶
Paystack Kenya fee matrix (charged to platform, passed to tenants as line item on payouts):
| Payment Method | Fee |
|---|---|
| Card (Visa/Mastercard) | 1.5% + KES 20 (capped at KES 2,500) |
| Bank Transfer | KES 50 flat |
| Mobile Money | 0.5% (no cap) |
| USSD | KES 30 flat |
Formula for fee estimate UI:
function estimatePaystackFee(amount: number, method: string): number {
switch (method) {
case 'card': return Math.min(amount * 0.015 + 20, 2500)
case 'bank_transfer': return 50
case 'mobile_money': return amount * 0.005
case 'ussd': return 30
default: return 0
}
}
Environment Variables¶
# Treasury API (.env / K8s secrets)
PAYSTACK_SECRET_KEY=sk_live_...
PAYSTACK_PUBLIC_KEY=pk_live_...
PAYSTACK_WEBHOOK_SECRET=<your webhook secret>
# Webhook and callback URLs are auto-generated from HTTP_PUBLIC_BASE_URL (TruLoad pattern):
# Webhook: {HTTP_PUBLIC_BASE_URL}/api/v1/webhooks/paystack
# Callback (user redirect): {HTTP_PUBLIC_BASE_URL}/api/v1/payments/callback
HTTP_PUBLIC_BASE_URL=https://booksapi.codevertexafrica.com
# Optional overrides (if not set, auto-generated URLs are used):
# PAYSTACK_CALLBACK_URL=...
# PAYSTACK_WEBHOOK_URL=...
References¶
- Paystack API Docs
- Paystack Dashboard
- Existing Paystack implementation:
finance-service/treasury-api/internal/modules/gateways/paystack.go - Existing Paystack webhook handler:
finance-service/treasury-api/internal/http/handlers/payments.go#PaystackWebhook