API Documentation
Accept payments through Once-Pay with one integration. Base URL: https://oncepay-api.owoade.dev
1. Authentication
Send your API key (from your Developer tab) as a bearer token on every request. Keys are scoped per merchant — you only ever see your own data.
Authorization: Bearer [API KEY]
2. Initiate a transaction
/transaction/initiateCreates a payment with your chosen gateway and returns a hosted payment link. Redirect your customer to it; the response comes back to your callback_url once resolved.
| Param | Type | Description |
|---|---|---|
| amount | number | Amount to charge, in Naira (NGN). |
| string | The customer's email address. | |
| payment_gateway | string | One of: paystack, flutterwave, korapay, monnify, seerbit. |
| callback_url | string · optional | Where your customer is redirected after payment; receives a verification call within 60s. |
| metadata | object · optional | Your freeform data, echoed back on webhooks and transaction reads. |
curl -X POST https://oncepay-api.owoade.dev/transaction/initiate \ -H "Authorization: Bearer [API KEY]" \ -H "Content-Type: application/json" \ -d '{ "amount": 15000, "email": "customer@example.com", "payment_gateway": "paystack", "callback_url": "https://your-server.com/payment-result", "metadata": { "order_id": "ORD-77" } }'
{ "status": "success", "message": "Operation successful", "data": { "payment_link": "https://checkout.paystack.com/…", "transaction_reference": "trx_1761234567890_42" } }
3. List your transactions
/transactionsNewest first. Only sanitized fields are returned — no gateway internals.
| Param | Type | Description |
|---|---|---|
| limit | number · optional | 1–50. Default 20. |
| offset | number · optional | Default 0. Add it to your limit to page. |
| status | string · optional | successful, pending, or failed. |
{ "status": "success", "data": { "total": 1, "transactions": [ { "id": 12, "reference": "trx_1761234567890_42", "amount": 15000, "payment_gateway": "paystack", "status": "successful", "customer_email": "customer@example.com", "createdAt": "2026-10-19 12:05:33" } ] } }
4. Verify a transaction
/transaction/verify/:trx_referenceRe-checks the transaction with its gateway and returns the current state, including the customer's email.
curl https://oncepay-api.owoade.dev/transaction/verify/trx_1761234567890_42 \ -H "Authorization: Bearer [API KEY]"
{ "status": "success", "data": { "reference": "trx_1761234567890_42", "amount": 15000, "payment_gateway": "paystack", "status": "successful", "customer_email": "customer@example.com", "resolved_in_secs": 27, "metadata": { "order_id": "ORD-77" }, "createdAt": "2026-10-19 12:05:33", "updatedAt": "2026-10-19 12:06:00" } }
{ "status": "error", "message": "Transaction not found", "data": null }
5. Webhooks
Set your webhook URL in the dashboard's Developer tab. When a transaction succeeds, Once-Pay POSTs:
POST https://your-server.com/webhooks/once-pay
Content-Type: application/json
X-Signature: 6f9a1c… (HMAC-SHA256 of the raw body below, keyed with your API key)
{
"event": "transaction.success",
"data": {
"id": 12,
"reference": "trx_1761234567890_42",
"amount": 15000,
"payment_gateway": "paystack",
"status": "successful",
"initiated_at_ms": 1761234567890,
"resolved_in_secs": 27,
"payment_made_at_ms": 1761234582000,
"callback_url": "https://your-server.com/payment-result",
"metadata": { "order_id": "ORD-77" },
"createdAt": "2026-10-19 12:05:33",
"updatedAt": "2026-10-19 12:06:00"
}
}Verify it before trusting the payload: the X-Signature header is an HMAC-SHA256 of the raw request body, keyed with your API key.
import crypto from "crypto"; const signature = req.headers["x-signature"]; // case-insensitive const expected = crypto .createHmac("sha256", process.env.ONCE_PAY_API_KEY) .update(rawRequestBody) // the exact bytes received, not re-stringified .digest("hex"); const valid = expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
Failed deliveries are retried on our reconciliation schedule — roughly every 30 seconds up to 5 times, then every 5 minutes up to 10, then hourly for the next 24 hours.
6. Response format
Every endpoint answers with the same envelope — check status first, then read data or message.
| Param | Type | Description |
|---|---|---|
| status | "success" | "error" | "error" means the request was rejected; "success" means data is populated. |
| message | string | Human-readable summary — the error reason when status is error. |
| data | object | null | The response payload; null whenever status is error. |
{ "status": "success", "message": "Initiate transaction successful", "data": { "payment_link": "https://checkout.paystack.com/…", "transaction_reference": "trx_1761234567890_42" } }
{ "status": "error", "message": "Transaction not found", "data": null }
7. JS Client SDK
Prefer not to hand-roll HTTP calls? The Once-Pay JS SDK wraps initiation and gives you a live settlement listener — no polling, no webhooks to verify on the client.
npm install once-pay-sdk socket.io-client # socket.io-client powers the live listenerimport OncePay, { OncePayError } from "once-pay-sdk"; const client = new OncePay({ base_url: "https://oncepay-api.owoade.dev", api_key: "[API KEY]", }); const response = await client.initiate_transaction({ amount: 15000, email: "customer@example.com", payment_gateway: "paystack", // one of the five gateways callback_url: "https://your-server.com/complete", // optional metadata: { order_id: "ORD-77" }, // optional on_success: ({ reference }) => { // fires at most once, when this transaction settles — // no polling, no reload. Subscribe later to any reference // with: client.watch_transaction(reference, callback) }, }); // open the hosted payment page (new tab) window.open(response.data.payment_link); // response.data.transaction_reference is also returned
// Everything failing throws OncePayError — check code before message. try { await client.initiate_transaction(payload); } catch (error) { if (error instanceof OncePayError) { error.code; // e.g. 401 (bad key), 400 (bad payload), 408 (window expired) error.message; // e.g. "Transaction not found" } }
Plain script tag (no bundler)? Load vanila-js/index.js instead — it pulls its Socket.IO client from a CDN automatically. Bundled apps must install socket.io-client themselves; the dynamic import is resolved at build time.
<script src="https://oncepay-storage.owoade.dev/lib/index.min.js"></script> <script> const client = new OncePay({ base_url: "https://oncepay-api.owoade.dev", api_key: "[API KEY]", }); document.getElementById("pay-button").addEventListener("click", () => { client .initiate_transaction({ amount: 15000, email: "customer@example.com", payment_gateway: "paystack", on_success: ({ reference }) => { // fires exactly once when the transaction settles window.location.href = "/order-complete?ref=" + reference; }, }) .then((response) => { // open the hosted payment page (new tab) window.open(response.data.payment_link); }) .catch((error) => { console.error(error.code, error.message); }); }); </script>
The SDK is served as a plain script from our CDN — OncePay and OncePayError become globals, no npm step, no bundler.
Errors
| Param | Type | Description |
|---|---|---|
| 400 | HTTP | Invalid payload (the message names the field). |
| 401 | HTTP | Missing or invalid API key. |
| 404 | HTTP | Transaction or resource not found. |
| 408 | HTTP | Callback/verification window expired (60s). |