Skip to main content
You can manage your webhook endpoints from the Webhook section in the dashboard.

Setting up endpoints

  1. Go to the Webhook section in your store’s dashboard.
  2. Add an endpoint URL (e.g. https://example.com/webhooks/zenobank).
  3. Select the event types you want to receive.

Events

EventDescription
checkout.completedThe full amount has been paid successfully.
checkout.expiredThe checkout expired without receiving any payment.
checkout.partially_paidThe checkout expired after receiving a partial payment (paidAmount < priceAmount).

Payload format

The customer paid the full amount. paidAmount is equal to or greater than priceAmount.
{
  "type": "checkout.completed",
  "data": {
    "id": "ch_l0k1o87yt6",
    "orderId": "order-12345",
    "priceCurrency": "USD",
    "priceAmount": "100.00",
    "paidAmount": "100.00",
    "status": "COMPLETED",
    "expiresAt": "2025-10-05T12:00:00Z",
    "checkoutUrl": "https://pay.zenobank.io/ch_l0k1o87yt6",
    "createdAt": "2025-10-04T10:00:00Z",
    "successRedirectUrl": "https://example.com/success"
  }
}

Verifying webhooks

Every webhook request includes three headers used for signature verification:
HeaderDescription
svix-idUnique message identifier
svix-timestampUnix timestamp of the message
svix-signatureHMAC signature
Use the official Svix library to verify signatures. To find your signing secret, go to Developers, click on a webhook endpoint, and copy the Signing Secret on the right side. It starts with whsec_.

Install the Svix library

bash pnpm add svix

Verify the signature

You must use the raw request body when verifying webhooks. The cryptographic signature is sensitive to even the slightest changes. Watch out for frameworks that parse the request as JSON and then stringify it, as this will break the signature verification.
import { Webhook } from 'svix';

const secret = process.env.ZENO_WEBHOOK_SECRET; // whsec_...

const payload = rawBody; // raw request body as string
const headers = {
  'svix-id': request.headers.get('svix-id'),
  'svix-timestamp': request.headers.get('svix-timestamp'),
  'svix-signature': request.headers.get('svix-signature'),
};

const wh = new Webhook(secret);

try {
  const msg = wh.verify(payload, headers);
  const { type, data } = msg;

  switch (type) {
    case 'checkout.completed':
      // Payment successful — fulfill the order
      console.log(`Order ${data.orderId} completed. Paid: ${data.paidAmount} ${data.priceCurrency}`);
      break;
    case 'checkout.expired':
      // Checkout expired — no payment was received
      console.log(`Order ${data.orderId} expired`);
      break;
    case 'checkout.partially_paid':
      // Partial payment received — consider issuing a refund
      console.log(`Order ${data.orderId} partially paid: ${data.paidAmount}/${data.priceAmount} ${data.priceCurrency}`);
      break;
  }
} catch (err) {
  // Signature verification failed
  console.error('Invalid webhook signature');
}
Always verify the signature before processing the event. Never trust the payload without verification. You must use the raw request body, do not parse the JSON before verifying.
For framework-specific examples (Express, Next.js, Django, Flask, Laravel, Rails, etc.), see the Svix verification docs.

FAQ

If Zeno Bank does not receive a 2xx response from your webhook endpoint, we will retry the delivery.Each message is attempted based on the following schedule, where each period starts after the failure of the preceding attempt:
  • 5 seconds
  • 5 minutes
  • 30 minutes
  • 2 hours
  • 5 hours
  • 10 hours
Respond with any 2xx status code to acknowledge the event. You can monitor delivery attempts and manually retry from the Developers section in the dashboard.
Zeno Bank webhooks provide at-least-once delivery. Every event will be delivered to your endpoint at least once, but may be delivered more than once in rare cases (such as network timeouts where your server processed the event but the acknowledgement was lost).To handle duplicates, use the svix-id header included with every webhook request. This is a unique identifier for each event delivery. Store processed svix-id values and skip any duplicates.
Yes. Go to Developers in the dashboard, click on your webhook endpoint, find the event you want to retry, and click the replay button to resend it.
No. If you use one of our official plugins (WooCommerce, PrestaShop, WHMCS, etc.), webhooks are configured automatically. You don’t need to set up endpoints or verify signatures manually.