Webhooks
QuataPay sends signed HTTP POST requests to your configured endpoint when payment events occur.
Configure an endpoint
In your merchant dashboard go to Developer → Webhooks and add your HTTPS endpoint URL. Only HTTPS endpoints are accepted.
Signature verification
Every webhook request includes an X-QuataPay-Signature header:
X-QuataPay-Signature: sha256=abc123…
The value is sha256= followed by the HMAC-SHA256 of the raw request body, keyed with your webhook secret. Always verify this before processing.
Node.js
const crypto = require("crypto");
function verifySignature(secret, rawBody, header) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(header),
);
}
Python
import hashlib, hmac
def verify_signature(secret: str, raw_body: bytes, header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header)
PHP
function verifySignature(string $secret, string $rawBody, string $header): bool {
$expected = "sha256=" . hash_hmac("sha256", $rawBody, $secret);
return hash_equals($expected, $header);
}
Events
payment.succeeded
Fired when a customer successfully pays a checkout intent.
{
"event": "payment.succeeded",
"data": {
"id": "pay_abc",
"slug": "abc123def456",
"status": "succeeded",
"amount": 5000,
"currency": "XAF",
"description": "Order #1042",
"customer_reference": "cust_abc123",
"transaction_id": "tx_xyz",
"metadata": {},
"created_at": "2025-05-11T09:00:00Z",
"completed_at": "2025-05-11T09:01:34Z"
}
}
payment.failed
Fired when a payment attempt fails (insufficient funds, PIN error, etc.).
{
"event": "payment.failed",
"data": {
"id": "pay_abc",
"slug": "abc123def456",
"status": "failed",
"failure_reason": "INSUFFICIENT_FUNDS",
…
}
}
payment.cancelled
Fired when a customer cancels or the intent TTL expires.
{
"event": "payment.cancelled",
"data": {
"id": "pay_abc",
"status": "cancelled",
…
}
}
Retry policy
If your endpoint does not return 2xx within 15 seconds, QuataPay retries with exponential back-off:
After 5 failed attempts the delivery is marked failed. You can view and retry deliveries in Developer → Webhooks → Delivery log.
Best practices
- Respond quickly (
2xx) and process asynchronously — do not block on business logic. - Always verify the signature before processing.
- Make your handler idempotent — the same event may be delivered more than once.
- Use the
idfield, not theslug, as a stable unique identifier for the payment.