Vexoli

Developers · Webhooks

Webhooks

JS callbacks are UX-only. The signed webhook is the only source of truth for whether a payment succeeded.

Events

payment.successPayment verified successfully. Fulfil the order.
payment.failedPayment failed or was rejected.
payment.expiredOrder expired without a verified payment.
payment.manual_reviewPayment needs extended verification. Do not fulfil yet.
settlement.processedA settlement batch was processed.

Signature verification

Vexoli signs each delivery as HMAC-SHA256 over timestamp + "." + raw body, using your webhook secret. Headers: X-Vexoli-Timestamp and X-Vexoli-Signature. Always use the raw request body and a constant-time comparison.

// Node.js (Express, raw body required)
const crypto = require("crypto");

function verify(req, secret) {
  const ts = req.headers["x-vexoli-timestamp"];
  const sig = req.headers["x-vexoli-signature"]; // hex HMAC-SHA256
  const raw = req.rawBody; // exact bytes received — do NOT re-serialise JSON
  const expected = crypto.createHmac("sha256", secret)
    .update(ts + "." + raw).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
// Reject events whose timestamp is more than 5 minutes old.

Retries

Return any 2xx quickly; do heavy work async. If delivery fails (non-2xx or timeout), Vexoli retries on this schedule, then marks the delivery as failed in the dashboard where you can replay it:

1 minute5 minutes15 minutes1 hour6 hours12 hours24 hours

Make your handler idempotent: the same event may be delivered more than once. Deduplicate by event / order ID.