Create a payment (Node.js)
import { randomUUID } from "node:crypto";
const res = await fetch("https://api.quatapay.com/api/v1/gateway/payments", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.QPAY_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": randomUUID(),
},
body: JSON.stringify({
amount: 1500,
currency: "XAF",
customer_reference: order.id,
return_url: "https://shop.example.com/pay/return",
cancel_url: "https://shop.example.com/pay/cancel",
}),
});
const { data } = await res.json();
return data.checkout_url;
Create a payment (Python)
import os, uuid, requests
resp = requests.post(
"https://api.quatapay.com/api/v1/gateway/payments",
headers={
"Authorization": f"Bearer {os.environ['QPAY_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"amount": 1500,
"currency": "XAF",
"customer_reference": order_id,
"return_url": "https://shop.example.com/pay/return",
"cancel_url": "https://shop.example.com/pay/cancel",
},
timeout=30,
)
resp.raise_for_status()
return resp.json()["data"]["checkout_url"]
Create a payment (PHP)
<?php
$ch = curl_init("https://api.quatapay.com/api/v1/gateway/payments");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("QPAY_KEY"),
"Content-Type: application/json",
"Idempotency-Key: " . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
"amount" => 1500,
"currency" => "XAF",
"customer_reference" => $orderId,
"return_url" => "https://shop.example.com/pay/return",
"cancel_url" => "https://shop.example.com/pay/cancel",
]),
]);
$res = json_decode(curl_exec($ch), true);
return $res["data"]["checkout_url"];
Verify a webhook signature
// Node — rawBody is the request body Buffer, exactly as received
import crypto from "node:crypto";
const timestamp = req.headers["x-quatapay-timestamp"] || "";
const provided = Buffer.from(req.headers["x-quatapay-signature"] || "");
// Refuse a replay: the timestamp must be within 300 seconds of now.
const fresh =
/^\d+$/.test(timestamp) &&
Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) <= 300;
// The signature covers "<timestamp>.<raw body>".
const expected = Buffer.from(
"sha256=" +
crypto
.createHmac("sha256", process.env.QPAY_WEBHOOK_SECRET)
.update(`${timestamp}.`)
.update(rawBody)
.digest("hex"),
);
if (
!fresh ||
expected.length !== provided.length ||
!crypto.timingSafeEqual(expected, provided)
) {
return res.status(400).end();
}