Documentation Index
Fetch the complete documentation index at: https://developer.fexpayments.com/llms.txt
Use this file to discover all available pages before exploring further.
The platform sends a signed HTTP POST to your callback_url whenever a payment changes state.
Payload
{
"event": "payment.completed",
"payment_intent_id": "pi_xxxx",
"merchant_id": "a1b2c3d4-...",
"amount": 29.99,
"currency": "USD",
"status": "completed",
"completed_at": "2025-01-01T12:31:55Z",
"reference": "ORDER-1042",
"metadata": { "table": "5", "cashier": "Maria" }
}
Webhook Events
| Event | Trigger |
|---|
payment.completed | Customer payment confirmed |
payment.expired | QR or approval window elapsed without payment |
payment.refunded | Merchant issued a refund |
Verifying the Signature
Every webhook delivery includes an X-Webhook-Signature header — an HMAC-SHA256 hex digest of the raw request body, signed with your counter’s webhook_secret.
Always use constant-time comparison (hmac.compare_digest / timingSafeEqual) to prevent timing attacks. Never use a simple string equality check.
Python:
import hmac, hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Node.js:
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
Acknowledging Delivery
Respond with HTTP 200 to acknowledge receipt. Deliveries returning a non-2xx status are retried up to 5 times with exponential backoff.