Webhooks
How you learn a payment succeeded. The only authoritative signal.
We POST a signed JSON body to the URL you give us, and retry for 24 hours across 12 attempts with exponential backoff. Return any 2xx to acknowledge.
POST https://your-server.example.com/webhooks/cleonpay
Content-Type: application/json
CleonPay-Signature: t=1755789326,v1=5257a869e7ecebeda32affa62cdca3fa...
CleonPay-Event-Type: payment.settled
{
"id": "evt_01H...",
"type": "payment.settled",
"created": "2026-08-21T13:15:26.438Z",
"data": { "payment_id": "3743881d-0fce-416c-b3de-8bc3f5d414b1" }
}
Events
| Event | Meaning |
|---|---|
payment.settled | Money moved. Fulfil here. |
payment.authorized | Customer paid; not yet settled |
payment.failed | Declined or errored |
payment.cancelled | Abandoned or cancelled |
payment.expired | Never completed |
refund.settled | Refund reached the customer |
payout.completed | Payout reached the recipient |
payout.returned | Payout bounced back |
Verifying the signature
Verify before you act. An endpoint that
trusts any POST can be told a payment succeeded by anyone who finds the URL.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret, header, rawBody, toleranceSeconds = 300) {
const parts = Object.fromEntries(
header.split(',').map((p) => p.split('=').map((s) => s.trim())),
);
if (!parts.t || !parts.v1) return false;
// Reject anything old, so a captured delivery cannot be replayed later.
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > toleranceSeconds) return false;
const expected = createHmac('sha256', secret)
.update(parts.t + '.' + rawBody)
.digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}
Use the raw body. Verify against the exact
bytes received. Parsing to JSON and re-serialising changes whitespace and key
order, and the signature will never match.
Duplicates
A retry after your server timed out means the same event arrives twice.
Deduplicate on the event id and treat a repeat as a no-op.