Most payment integrations follow the same painful path: you build a checkout UI, set up bank account display logic, handle Mobile Money prompts, manage session timers, poll for confirmation, and somehow do all of this correctly across every edge case. It takes days, sometimes weeks — and that's before you think about testing.
Afriex Checkout Sessions changes that. With a single API call, you get a fully hosted payment page that handles everything: payment method selection, bank transfer details, Mobile Money authorization, session expiration, and redirect on completion. You stay focused on your product; Afriex handles the payment flow.
This guide walks you through a complete integration from scratch — using either the official TypeScript SDK or the REST API directly.
How It Works
The flow is four steps:
- Your server creates a checkout session via the SDK or REST API
-
Afriex returns a
checkoutUrl - You redirect the customer to that URL
- After payment, Afriex redirects the customer back to your app and fires a webhook to your server
That's the entire integration surface. No payment UI to build. No polling loop. No bank account display to manage.
Prerequisites
Before you start, you'll need:
- An Afriex Business account with API access
- Your API key from the Afriex dashboard (Developer tab)
- Node.js 20+ if you're using the SDK, or any server-side runtime for the REST API
- An HTTPS redirect URL where customers will land after payment
Sandbox first. Checkout Sessions is currently available for end-to-end testing in Afriex's sandbox environment. Production rollout is happening soon — use sandbox to validate your integration now so you're ready to flip the switch.
Setup
Using the SDK (Node.js / TypeScript)
Install the official Afriex SDK:
npm install @afriex/sdk
# or
pnpm add @afriex/sdk
# or
yarn add @afriex/sdk
Then initialize it once — typically in a shared module or service file:
import { AfriexSDK } from "@afriex/sdk";
export const afriex = new AfriexSDK({
apiKey: process.env.AFRIEX_API_KEY,
environment: "staging", // or "production"
});
The SDK maps staging to https://sandbox.api.afriex.com and production to https://api.afriex.com — no need to manage base URLs yourself.
Enable retries for production resilience:
export const afriex = new AfriexSDK({
apiKey: process.env.AFRIEX_API_KEY,
environment: "production",
retryConfig: {
maxRetries: 3,
retryDelay: 1000, // milliseconds
retryableStatusCodes: [408, 429, 500, 502, 503, 504],
},
});
Using the REST API directly
Set your base URL depending on your environment:
Sandbox: https://sandbox.api.afriex.com
Production: https://api.afriex.com
Pass your API key on every request via the x-api-key header.
Step 1: Create a Checkout Session
SDK (Node.js / TypeScript)
import { afriex } from "./afriex-client"; // your initialized SDK instance
async function createCheckoutSession(order: Order) {
const session = await afriex.checkout.createSession({
amount: order.amountInKobo, // e.g. 500000 for ₦5,000
currency: "NGN",
merchantReference: order.id, // must be unique per session
redirectUrl: "https://yourapp.com/checkout/return",
channels: ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
customer: {
name: order.customer.name,
email: order.customer.email,
phone: order.customer.phone, // E.164 format: +2348100000000
countryCode: "NG",
},
metadata: {
orderId: order.id,
plan: order.plan,
},
});
return session.checkoutUrl;
}
The SDK returns { checkoutUrl: string } — redirect your customer there immediately.
REST API
cURL
curl -X POST https://sandbox.api.afriex.com/api/v1/checkout-session \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"amount": 500000,
"currency": "NGN",
"merchantReference": "order-2026-05-30-001",
"redirectUrl": "https://yourapp.com/checkout/return",
"channels": ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
"customer": {
"name": "Amarachi Okafor",
"email": "amara@example.com",
"phone": "+2348100000000",
"countryCode": "NG"
},
"metadata": {
"orderId": "ORD-9912",
"plan": "pro"
}
}'
Node.js (fetch)
async function createCheckoutSession(order) {
const response = await fetch(
"https://sandbox.api.afriex.com/api/v1/checkout-session",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.AFRIEX_API_KEY,
},
body: JSON.stringify({
amount: order.amountInKobo,
currency: "NGN",
merchantReference: order.id,
redirectUrl: "https://yourapp.com/checkout/return",
channels: ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
customer: {
name: order.customer.name,
email: order.customer.email,
phone: order.customer.phone,
countryCode: "NG",
},
metadata: {
orderId: order.id,
},
}),
}
);
const { data } = await response.json();
return data.checkoutUrl;
}
Python (requests)
import requests
import os
def create_checkout_session(order):
response = requests.post(
"https://sandbox.api.afriex.com/api/v1/checkout-session",
headers={
"Content-Type": "application/json",
"x-api-key": os.environ["AFRIEX_API_KEY"],
},
json={
"amount": order["amount_in_kobo"],
"currency": "NGN",
"merchantReference": order["id"],
"redirectUrl": "https://yourapp.com/checkout/return",
"channels": ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
"customer": {
"name": order["customer"]["name"],
"email": order["customer"]["email"],
"phone": order["customer"]["phone"],
"countryCode": "NG",
},
},
)
return response.json()["data"]["checkoutUrl"]
Response
{
"data": {
"checkoutUrl": "https://sandbox.pay.afriex.com/pay/eyJhbGciOiJI..."
}
}
Redirect your customer to checkoutUrl immediately. The session has an expiration timer, so don't hold on to the URL for later.
Request Reference
Required Fields
| Field | Type | Description |
|---|---|---|
amount |
integer | Amount in minor currency units (kobo for NGN, cents for USD). Minimum: 100. |
currency |
string | ISO 4217 currency code, uppercase (e.g. NGN, GHS). Must be enabled for checkout on your account. |
merchantReference |
string | Your unique identifier for this session. Used to match webhooks and reconcile transactions. |
redirectUrl |
string | HTTPS URL the customer is sent to after the checkout flow completes. |
customer |
object | Customer details. See below. |
Customer Object
| Field | Type | Description |
|---|---|---|
name |
string | Customer's full name. |
email |
string | Customer's email address. |
phone |
string | Phone number in E.164 format (e.g. +2348100000000). |
countryCode |
string | ISO 3166-1 alpha-2 country code (e.g. NG, GH). Case-insensitive. |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
channels |
array | ["VIRTUAL_BANK_ACCOUNT"] |
Payment methods to offer. Options: VIRTUAL_BANK_ACCOUNT, MOBILE_MONEY. |
metadata |
object | — | Flat key/value pairs (strings only) you want attached to the session for your own reference. |
Step 2: The Customer Completes Payment
Once redirected, the customer lands on the Afriex-hosted checkout page. Depending on the channels you enabled, they can:
Pay by Bank Transfer
The customer sees a one-time virtual bank account — account name, number, and bank — with the exact amount to transfer. They open their banking app, send the transfer, and return to the checkout page. Afriex detects the inbound transfer and confirms payment automatically.
⚠️ Exact amount required. The customer must transfer the precise amount shown. Sending a different amount will cause delays and may require a manual reconciliation or refund.
Pay by Mobile Money
The customer selects their network (e.g. MTN, Airtel), enters their account name and phone number, and taps Pay now. Afriex pushes an authorization prompt to their phone. Once they approve it, the checkout page updates automatically.
Both paths end in one of two outcomes: a success screen (auto-redirects to your redirectUrl) or a failure screen (with guidance to retry or contact support).
Step 3: Handle the Redirect
When the customer lands back on your redirectUrl, show them an appropriate confirmation or error screen. Use the merchantReference you generated to look up the order state in your system.
A typical return handler:
// Express.js example
app.get("/checkout/return", async (req, res) => {
// The merchantReference you passed when creating the session
const reference = req.query.reference;
// Look up your order by reference — don't trust query params for payment status
const order = await db.orders.findByReference(reference);
if (order.status === "paid") {
return res.redirect(`/orders/${order.id}/confirmation`);
}
// Not yet confirmed — webhook may still be in flight
return res.redirect(`/orders/${order.id}/pending`);
});
Don't use the redirect to confirm payment. The redirect tells you the customer finished the checkout flow, not that payment succeeded. Always use the webhook (see Step 4) as the authoritative signal.
Step 4: Listen for the Webhook
When Afriex confirms payment, it fires a CHECKOUT_SESSION.CREATED webhook to the callback URL configured in your Afriex dashboard. This is the source of truth for marking an order as paid.
// Express.js webhook handler example
app.post("/webhooks/afriex", express.raw({ type: "application/json" }), async (req, res) => {
const event = JSON.parse(req.body);
if (event.type === "CHECKOUT_SESSION.CREATED") {
const { merchantReference, amount, currency } = event.data;
// Mark order as paid using your merchantReference
await db.orders.markPaid({
reference: merchantReference,
amount,
currency,
});
}
res.status(200).send("OK");
});
Make your webhook handler idempotent — Afriex may retry delivery if your endpoint doesn't respond with a 200. Using merchantReference as your idempotency key is the right approach.
Beyond the Basic Flow: Advanced Use Cases
The checkout session API supports a few patterns beyond the standard redirect flow that are worth knowing about.
Delayed Payment Collection
You don't have to redirect customers immediately after creating a session. You can create the session on your server, store the checkoutUrl, and send it to the customer later — via email, SMS, or a payment link in an invoice. This is useful for billing flows where payment isn't expected at the point of order.
// Create the session when the invoice is generated
const session = await afriex.checkout.createSession({ ...invoiceDetails });
// Store checkoutUrl against the invoice
await db.invoices.update(invoice.id, { paymentUrl: session.checkoutUrl });
// Send it later when the invoice is dispatched
await mailer.send({
to: invoice.customer.email,
subject: "Your invoice is ready",
body: `Pay here: ${invoice.paymentUrl}`,
});
Keep session expiration in mind — if the payment link will be sent days later, build in a check for whether the session is still valid before sending.
Embedded Checkout (Iframe / Webview)
Rather than fully leaving your app, you can open the checkoutUrl inside an iframe (web) or a webview (mobile). This keeps the customer inside your product's shell while Afriex handles the payment UI.
<!-- Web: iframe embed -->
<iframe
src="https://sandbox.pay.afriex.com/pay/YOUR_SESSION_TOKEN"
width="100%"
height="600"
frameborder="0"
allow="payment"
></iframe>
// React Native: WebView embed
import { WebView } from "react-native-webview";
<WebView
source={{ uri: session.checkoutUrl }}
onNavigationStateChange={(state) => {
// Detect redirect back to your redirectUrl
if (state.url.startsWith("https://yourapp.com/checkout/return")) {
navigation.navigate("OrderConfirmation");
}
}}
/>
The webhook still fires regardless of how the customer accesses the checkout — embedded or full redirect.
Important Notes
Amounts are always in minor units
Afriex uses the smallest denomination of each currency — no decimals in the amount field:
| Currency | Major unit | Minor unit | Example |
|---|---|---|---|
| NGN | ₦1 | 1 kobo | ₦5,000 → 500000
|
| GHS | ₵1 | 1 pesewa | ₵100 → 10000
|
| USD | $1 | 1 cent | $10 → 1000
|
The minimum amount is 100 (equivalent to 1 major unit).
merchantReference must be unique
Each session needs a distinct merchantReference. Re-using a reference from a previous session will result in an error. Use your internal order ID, a UUID, or any other identifier you control.
Sessions expire
Each checkout session has an expiration timer visible to the customer. If the session expires before payment completes, the customer will need a new session. Build a recovery path — for example, let customers re-initiate checkout from your order page.
Phone numbers must be E.164
The customer's phone must be in E.164 format: + followed by the country code and subscriber number, no spaces or punctuation. For example, a Nigerian number would be +2348100000000, not 08100000000.
Testing in Sandbox
All of the above works end-to-end in sandbox today:
- Use base URL
https://sandbox.api.afriex.com(or setenvironment: "staging"in the SDK) - Use your sandbox API key from the Afriex dashboard
- Successful checkout sessions redirect to
https://sandbox.pay.afriex.com/pay/{token} - Webhooks fire to your configured callback URL in sandbox as well
Test the full round-trip — create a session, go through the hosted checkout, and verify your webhook handler receives and processes the event — before switching to production credentials.
Error Responses
| Status | Meaning |
|---|---|
400 |
Invalid request — check required fields, amount minimum, and currency format |
401 |
Unauthorized — verify your x-api-key header |
500 |
Server error — retry with exponential backoff |
What's Next
- Afriex SDK Documentation — Full SDK reference covering installation, configuration, and all available modules
- SDK Checkout Reference — SDK-specific parameters and examples for the Checkout API
- User Flow Guide — See the full customer-facing checkout experience, screen by screen
- Webhooks — Configure your callback URL and understand the full webhook payload
- REST API Reference — Full OpenAPI spec for the Create Checkout Session endpoint
Afriex Checkout Sessions is currently available in sandbox. Production rollout is expected in the coming weeks — get your integration ready today.
Top comments (0)