QuataPay
Developer

Initiate a payment

The end-to-end flow for taking a payment from your customer.

Browse developer docs
Back to developer docs

1. Create a payment intent

POST https://api.quatapay.com/api/v1/gateway/payments
Authorization: Bearer qpay_live_xxx
Content-Type: application/json
Idempotency-Key: 9b2c4f3e-...

{
  "amount": 1500,
  "currency": "XAF",
  "customer_reference": "order_12345",
  "description": "Lunch combo #3",
  "return_url": "https://shop.example.com/pay/return?order=order_12345",
  "cancel_url": "https://shop.example.com/pay/cancel?order=order_12345"
}

amount is an integer number of XAF. XAF has no smaller unit, so never send decimals and never multiply by 100. Unknown fields are refused with 422.

With a test key (qpay_test_…), use the sandbox path instead — POST https://api.quatapay.com/api/v1/gateway/sandbox/payments. It mirrors the live API exactly; only the path and key prefix differ.

2. Redirect the customer

The response is an envelope with a nested payment object and a hosted data.checkout_url. Redirect the customer to it. They pay with MTN Mobile Money (no QuataPay account needed) or from a QuataPay wallet. For Mobile Money the page shows a payer fee on top of your amount (currently 1.3%, set by QuataPay) before they confirm. Then the browser is sent to your return_url, cancel_url or failure_url exactly as you wrote it: QuataPay adds no query parameters, so put your own order id in the URL.

{
  "success": true,
  "data": {
    "payment": {
      "id": "3f2b8c1e-5d7a-4e0b-9a61-2c4d8e7f9a10",
      "slug": "Xb3kP0qLm7Rt2VwY9cZa4Hn1",
      "status": "requested",
      "amount": 1500,
      "currency": "XAF"
    },
    "checkout_url": "https://quatapay.com/checkout/Xb3kP0qLm7Rt2VwY9cZa4Hn1"
  },
  "error": null
}

3. Trust the webhook, not the redirect

The redirect tells the customer the payment finished. The webhook tells your server it actually settled. Listen for gateway.payment.succeeded before you fulfil the order — never rely on the redirect alone, since the customer can bookmark or share the success URL.

Idempotency

Send a UUID in the Idempotency-Key header on every create call: one key per checkout attempt, reused only when you retry that same attempt. We cache the response for 24 hours, so a retry returns the same payment instead of creating a second one. The same key with a different body answers 409.