API Reference
The Drexpay API lets you create payment links, request withdrawals, and receive real-time webhook events when transactions complete.
Authentication
All API requests must include your secret key in the Authorization header. Keep your secret key on the server — never expose it in client-side code or browsers.
Authorization: Bearer sk_live_YOUR_SECRET_KEY
Create a payment link
https://drexpay.tech/api/payment-linksCreates a new pending payment and returns a hosted checkout URL to redirect your customer to.
POST https://drexpay.tech/api/payment-links
Authorization: Bearer sk_live_...
Content-Type: application/json
{
"email": "customer@example.com",
"amount": 25000,
"redirectTo": "https://yourapp.com/order/complete"
}Request body
emailrequired | string | The customer's email address. They'll receive a confirmation when payment is detected. |
amountrequired | integer | Amount in Naira (NGN). Must be a whole number — no decimals. E.g. 25000 for ₦25,000. |
redirectTorequired | string | URL to redirect the customer to after their payment is confirmed. |
Response
{
"id": "clx4f2g0000abc123",
"url": "https://drexpay.tech/pay/clx4f2g0000abc123",
"reference": "PAY-1A2B3C4D",
"amount": 25000,
"email": "customer@example.com"
}Redirect your customer to url. They will see the exact transfer amount and bank account details. Store reference against your order — you'll receive it back in the webhook.
Payment lifecycle
A payment moves through the following statuses. Your webhook fires on approved and declined.
Payment created. Waiting for customer to transfer.
Transfer detected and confirmed. Webhook fires with charge.success.
Payment was manually declined by the gateway. Webhook fires with charge.failed.
No transfer received within 30 minutes. No webhook is sent.
Create a withdrawal
https://drexpay.tech/api/withdrawalRequests a payout to a bank account. The withdrawal is queued for processing and you'll receive a webhook when it's sent or declined.
POST https://drexpay.tech/api/withdrawal
Authorization: Bearer sk_live_...
Content-Type: application/json
{
"email": "customer@example.com",
"accountName": "John Doe",
"accountNumber": "0123456789",
"bank": "GTBank",
"amount": 15000,
"transactionId": "your-unique-tx-id-001"
}Request body
emailrequired | string | Recipient's email address. They'll be notified when the withdrawal is processed. |
accountNamerequired | string | Exact name on the bank account. |
accountNumberrequired | string | 10-digit NUBAN account number. |
bankrequired | string | Bank name. E.g. GTBank, Access Bank, OPay. |
amountrequired | integer | Amount in Naira to withdraw. |
transactionIdrequired | string | Your unique identifier for this withdrawal. Used for idempotency — submitting the same ID twice returns the original. |
Response
{
"id": "clx4g1h0000xyz789",
"status": "requested",
"amount": 15000,
"bank": "GTBank",
"accountNumber": "0123456789"
}Webhooks
Drexpay sends a signed POST request to your webhook URL when a payment or withdrawal status changes. Configure your webhook URLs and secrets in your dashboard settings.
Payment webhook — charge.success
{
"event": "charge.success",
"data": {
"reference": "PAY-1A2B3C4D"
}
}Withdrawal webhook
{
"transactionId": "your-unique-tx-id-001",
"amount": 15000,
"status": "sent" // or "declined"
}Verifying signatures
Every webhook request includes a signature header. Always verify it before processing the event.
Payment webhook — header: x-drexpay-signature
import { createHmac } from "crypto";
function verifyPaymentWebhook(rawBody: string, signature: string, secret: string) {
const expected = createHmac("sha512", secret)
.update(rawBody)
.digest("hex");
return signature === expected;
}Withdrawal webhook — header: x-drexpay-withdrawal-signature
import { createHmac } from "crypto";
function verifyWithdrawalWebhook(rawBody: string, signature: string, secret: string) {
const expected = createHmac("sha512", secret)
.update(rawBody)
.digest("hex");
return signature === expected;
}Responding to webhooks
Return a 200 status as quickly as possible. Do your processing asynchronously — if your endpoint takes too long or returns a non-2xx status, delivery may be retried.
Mobile SDKs
Drexpay provides native SDKs for React Native and Flutter. Both work the same way: your backend creates a payment link, the SDK opens the hosted checkout in a WebView, and calls onSuccess when the customer is redirected after payment is confirmed.
How it works
- 1Your mobile app calls your own backend to initiate a payment.
- 2Your backend calls POST /api/payment-links with your secret key and returns the checkout URL.
- 3The SDK opens the URL in a WebView — the customer sees the payment instructions.
- 4When payment is confirmed, Drexpay redirects the customer to your redirectTo URL.
- 5The SDK intercepts the redirect and calls onSuccess(reference).
- 6Your backend receives a signed webhook confirming the payment — use this for fulfillment.
url to the mobile app.Install
npm install drexpay-react-native react-native-webview # or yarn add drexpay-react-native react-native-webview # iOS — run pod install after cd ios && pod install
1 — Create the payment link on your server
Your API key lives only here, never in the app bundle.
// pages/api/create-payment.ts (your Next.js / Express backend)
// NEVER put your Drexpay API key in the mobile app
export async function POST(req) {
const { amount, email } = await req.json();
const res = await fetch("https://drexpay.tech/api/payment-links", {
method: "POST",
headers: {
"Authorization": "Bearer " + process.env.DREXPAY_SECRET_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
amount, // whole number, e.g. 25000 for ₦25,000
redirectTo: "https://yourapp.com/payment-done",
}),
});
const { id, url, reference } = await res.json();
return Response.json({ url, reference });
}2 — Open the checkout in your app
import { useState } from "react";
import { View, Text, Pressable, StyleSheet } from "react-native";
import { DrexpayCheckout } from "drexpay-react-native";
export function OrderScreen() {
const [checkoutUrl, setCheckoutUrl] = useState<string | null>(null);
const [reference, setReference] = useState<string | null>(null);
async function pay() {
// Call YOUR backend — never call Drexpay directly from the app
const res = await fetch("https://yourapp.com/api/create-payment", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amount: 25000, email: "customer@example.com" }),
});
const { url, reference } = await res.json();
setReference(reference);
setCheckoutUrl(url);
}
function handleSuccess(ref: string) {
setCheckoutUrl(null);
// ref may be empty if Drexpay doesn't append it to your redirectTo URL —
// use the reference you stored above, or rely on your server-side webhook.
console.log("Payment done, reference:", ref || reference);
}
return (
<View style={styles.container}>
<Pressable style={styles.btn} onPress={pay}>
<Text style={styles.btnText}>Pay ₦25,000</Text>
</Pressable>
<DrexpayCheckout
visible={!!checkoutUrl}
checkoutUrl={checkoutUrl ?? ""}
onSuccess={handleSuccess}
onClose={() => setCheckoutUrl(null)}
onError={(err) => console.error("Checkout error:", err)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: "center", justifyContent: "center" },
btn: {
backgroundColor: "#7C3AED",
paddingHorizontal: 24,
paddingVertical: 14,
borderRadius: 12,
},
btnText: { color: "#fff", fontWeight: "600", fontSize: 16 },
});3 — Verify the webhook on your server
Always fulfill the order from the webhook, not just from onSuccess.
import { createHmac } from "crypto";
// In your webhook endpoint
app.post("/webhooks/drexpay", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-drexpay-signature"];
const expected = createHmac("sha512", process.env.DREXPAY_WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
if (sig !== expected) return res.status(401).end();
const { event, data } = JSON.parse(req.body);
if (event === "charge.success") {
// Fulfill the order for data.reference
await fulfillOrder(data.reference);
}
res.status(200).end();
});Errors
All errors return a JSON object with an error field describing the problem.
| Status | Meaning |
|---|---|
400 | Bad request — a required field is missing or malformed. |
401 | Unauthorized — your API key is missing or invalid. |
403 | Forbidden — your key doesn't have permission for this action. |
404 | Not found — the requested resource doesn't exist. |
409 | Conflict — a resource with this ID already exists (e.g. duplicate transactionId). |
429 | Rate limited — slow down and retry after a short delay. |
500 | Server error — something went wrong on our end. Contact support if it persists. |
// Example error response
{
"error": "amount is required and must be a positive integer"
}