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",
"cancel_url": "https://shop.example.com/pay/cancel"
}Amount is in the smallest currency unit (whole XAF for now — sub-unit support coming with the multi-currency rollout).
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 confirm in their QuataPay app or with mobile money, then are returned to your return_url or cancel_url.
{
"success": true,
"data": {
"payment": {
"id": "pay_01HZ...",
"slug": "abcd1234",
"status": "requested",
"amount": 1500,
"currency": "XAF"
},
"checkout_url": "https://quatapay.com/checkout/abcd1234"
},
"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 fresh UUID in Idempotency-Key on every create call. We cache the response for 24 hours so retries don't double-charge the customer — replays return the exact same payload.