CleonPay

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

EventMeaning
payment.settledMoney moved. Fulfil here.
payment.authorizedCustomer paid; not yet settled
payment.failedDeclined or errored
payment.cancelledAbandoned or cancelled
payment.expiredNever completed
refund.settledRefund reached the customer
payout.completedPayout reached the recipient
payout.returnedPayout 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.