Webhook signatures
Every webhook is signed with HMAC-SHA256 over the raw request body using your webhook secret. The signature is sent in the X-ZyndPay-Signature header. Compare using constant-time comparison. Retry: exponential backoff up to 24h.
Choose your language:
Verify Zyndpay-Signature
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const SECRET = process.env.ZYNDPAY_WEBHOOK_SECRET!;
const TOLERANCE_SECONDS = 300; // reject events older than 5 minutes
// Mount raw-body parser so we can hash the exact bytes ZyndPay signed.
app.post('/webhooks/zyndpay', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.header('zyndpay-signature') ?? '';
const parts = Object.fromEntries(
header.split(',').map((kv) => kv.split('=', 2) as [string, string]),
);
const timestamp = parts.t;
const received = parts.v1;
if (!timestamp || !received) return res.status(401).send('malformed signature');
if (Math.abs(Date.now() / 1000 - Number(timestamp) / 1000) > TOLERANCE_SECONDS) {
return res.status(401).send('timestamp out of tolerance');
}
const expected = crypto
.createHmac('sha256', SECRET)
.update(`${timestamp}.${req.body.toString('utf8')}`)
.digest('hex');
const a = Buffer.from(received);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send('invalid signature');
}
const event = JSON.parse(req.body.toString('utf8'));
// handle event.event (e.g. 'payin.confirmed') ...
res.status(200).send('ok');
});Event catalog
Every webhook is a JSON envelope with three fields — event, data, and createdAt. The event field carries the type (e.g. payin.confirmed). Replays are safe — the same delivery id is retried on failure.
| event | description |
|---|---|
| payin.created | Pay-in created — TRON address minted, awaiting payment. |
| payin.confirming | On-chain transfer seen, waiting for required confirmations. |
| payin.confirmed | Customer paid and the transfer settled. Amount and tx hash are in data. |
| payin.expired | Address expired before the customer completed payment. |
| payin.failed | Underlying transfer failed or was rejected. |
| payin.overpaid | Customer paid more than amountRequested. |
| payin.underpaid | Customer paid less than amountRequested. |
| deposit.confirmed | Consumer wallet top-up confirmed on-chain. |
| deposit.failed | Consumer wallet top-up failed. |
| deposit.overpaid | Consumer top-up exceeded the requested amount. |
| deposit.underpaid | Consumer top-up was below the requested amount. |
| payout.broadcast | Payout signed and broadcast to TRON. |
| payout.confirmed | Payout confirmed on-chain. |
| payout.failed | Payout rejected (insufficient balance, bad address, compliance). |
| withdrawal.requested | Merchant withdrawal request submitted. |
| withdrawal.approved | Withdrawal approved by ZyndPay. |
| withdrawal.broadcast | Withdrawal signed and broadcast to TRON. |
| withdrawal.confirmed | Withdrawal confirmed on-chain. |
| withdrawal.failed | Withdrawal rejected by the gateway or compliance. |
| refund.created | Refund created — pending approval. |
| refund.approved | Refund approved. |
| refund.rejected | Refund request rejected. |
| refund.completed | Refund sent back to the original payer. |
| refund.failed | Refund attempt failed. |
| subscription.created | Recurring subscription created. |
| subscription.renewal_initiated | Renewal cycle started. |
| subscription.renewed | Recurring subscription charge succeeded. |
| subscription.failed | Renewal charge failed. |
| subscription.updated | Subscription details changed. |
| subscription.paused | Subscription paused. |
| subscription.resumed | Subscription resumed. |
| subscription.cancelled | Subscription cancelled. |
| conversion.confirmed | Wallet conversion completed. |
| conversion.failed | Wallet conversion failed. |
| dispute.opened | A dispute was opened against a payment. |
| dispute.resolved | A dispute was resolved. |
| dispute.rejected | A dispute was rejected. |
| dispute.escalated | A dispute was escalated to a higher level. |
| aml.flagged | Transaction flagged by AML screening. |
| splitpayment.created | Marketplace split-payment created. |