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.

header
Authorization: Bearer [API KEY]

2. Initiate a transaction

POST/transaction/initiate

Creates 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.

ParamTypeDescription
amountnumberAmount to charge, in Naira (NGN).
emailstringThe customer's email address.
payment_gatewaystringOne of: paystack, flutterwave, korapay, monnify, seerbit.
callback_urlstring · optionalWhere your customer is redirected after payment; receives a verification call within 60s.
metadataobject · optionalYour freeform data, echoed back on webhooks and transaction reads.
request
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" }
  }'
response · 200
{
  "status": "success",
  "message": "Operation successful",
  "data": {
    "payment_link": "https://checkout.paystack.com/…",
    "transaction_reference": "trx_1761234567890_42"
  }
}

3. List your transactions

GET/transactions

Newest first. Only sanitized fields are returned — no gateway internals.

ParamTypeDescription
limitnumber · optional1–50. Default 20.
offsetnumber · optionalDefault 0. Add it to your limit to page.
statusstring · optionalsuccessful, pending, or failed.
response · 200
{
  "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

GET/transaction/verify/:trx_reference

Re-checks the transaction with its gateway and returns the current state, including the customer's email.

request
curl https://oncepay-api.owoade.dev/transaction/verify/trx_1761234567890_42 \
  -H "Authorization: Bearer [API KEY]"
response · 200
{
  "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"
  }
}
response · 404
{
  "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:

webhook request
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.

signature verification (node)
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.

ParamTypeDescription
status"success" | "error""error" means the request was rejected; "success" means data is populated.
messagestringHuman-readable summary — the error reason when status is error.
dataobject | nullThe response payload; null whenever status is error.
success
{
  "status": "success",
  "message": "Initiate transaction successful",
  "data": {
    "payment_link": "https://checkout.paystack.com/…",
    "transaction_reference": "trx_1761234567890_42"
  }
}
error
{
  "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.

install
npm install once-pay-sdk socket.io-client   # socket.io-client powers the live listener
checkout.js
import 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
errors
// 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.

index.html
<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

ParamTypeDescription
400HTTPInvalid payload (the message names the field).
401HTTPMissing or invalid API key.
404HTTPTransaction or resource not found.
408HTTPCallback/verification window expired (60s).

Need an API key? Sign in and open your Developer tab.