QPay

BUILD WITH QPAY

One API.
Your payment experience.

Use QPay’s hosted checkout to keep payment authorisation with the payment provider. This release supports sandbox tests only; no real funds move. API keys belong on your server.

1 Apply and configure your account

  1. Apply for a merchant account. Sign in to follow approval.
  2. After approval, open Settings and save your exact HTTPS return URL.
  3. Open Developers in your dashboard. Create a test API key and register your HTTPS webhook endpoint.
  4. Copy the key and signing secret into your server’s private configuration. Secrets are shown once.

2 Create a payment on your server

Amounts are integers in the currency’s minor units. Reuse the same idempotency key when retrying the same order. Changing an amount or reference requires a new key.

POST https://qpay.africa/v1/payment-intents
Authorization: Bearer YOUR_TEST_KEY
Content-Type: application/json
Idempotency-Key: order-1048

{
  "amount": 12500,
  "currency": "ZAR",
  "country": "ZA",
  "merchant_reference": "order-1048"
}

Redirect your customer to the returned checkout_url. The checkout expires after 30 minutes. Only your configured return URL is allowed. A browser return is not evidence of payment.

3 Verify signed notifications

QPay POSTs events to your endpoint with QPAY-Timestamp and QPAY-Signature headers. Verify the HMAC SHA256 of the exact timestamp, a dot, and the raw request body, using your webhook secret. Use a constant-time comparison and reject timestamps older than five minutes.

// PHP webhook verification — read the raw body exactly once
$raw = file_get_contents('php://input');
$t = $_SERVER['HTTP_QPAY_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_QPAY_SIGNATURE'] ?? '';
if (!ctype_digit($t) || abs(time() - (int)$t) > 300 ||
    !hash_equals(hash_hmac('sha256', $t.'.'.$raw, $secret), $sig)) {
    http_response_code(401); exit;
}
$event = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
// Persist event.id under a UNIQUE constraint, atomically with fulfilment.
// Verify payment ID, expected amount, currency and order reference.
// Return a 2xx only after committing the durable update.

Events are payment_intent.succeeded and payment_intent.failed. Handle duplicate and out-of-order delivery. Never overwrite a succeeded payment with a later failure. Return 2xx after a durable update. QPay retries failures up to 12 times with increasing delays; delivery history appears in your dashboard.

4 Retrieve the payment before fulfilment

GET https://qpay.africa/v1/payment-intents/PAYMENT_ID
Authorization: Bearer YOUR_TEST_KEY

Verify the payment’s status, amount, currency and merchant reference against your order before granting access or shipping. Another merchant’s key cannot read your payment. Keep polling and webhook handling idempotent.

5 Run end to end sandbox tests

Create an order, open checkout and submit a sandbox method. A QPay operator must send the signed sandbox provider result using the included test command. Test success, failure, duplicate callbacks, mismatched amounts, expired sessions and cross-merchant access before launch.

Production readiness

Approval unlocks sandbox credentials. Live keys remain blocked in this release. Provider contracts, merchant receiving accounts, provider adapters and certification, subscription collection configuration, and operational acceptance are required before live payments can be enabled.

Apply for developer access ↗