Signatures de webhooks

Chaque webhook est signé avec HMAC-SHA256 sur le corps brut de la requête en utilisant votre secret de webhook. La signature est envoyée dans l'en-tête X-ZyndPay-Signature. Comparez en temps constant. Relance : backoff exponentiel sur 24h.

Choisissez votre langage :

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');
});

Catalogue d'événements

Chaque webhook est une enveloppe JSON à trois champs — event, data et createdAt. Le champ event porte le type (ex. payin.confirmed). Les renvois sont sûrs — le même delivery id est réessayé en cas d'échec.

eventdescription
payin.createdPay-in created — TRON address minted, awaiting payment.
payin.confirmingOn-chain transfer seen, waiting for required confirmations.
payin.confirmedCustomer paid and the transfer settled. Amount and tx hash are in data.
payin.expiredAddress expired before the customer completed payment.
payin.failedUnderlying transfer failed or was rejected.
payin.overpaidCustomer paid more than amountRequested.
payin.underpaidCustomer paid less than amountRequested.
deposit.confirmedConsumer wallet top-up confirmed on-chain.
deposit.failedConsumer wallet top-up failed.
deposit.overpaidConsumer top-up exceeded the requested amount.
deposit.underpaidConsumer top-up was below the requested amount.
payout.broadcastPayout signed and broadcast to TRON.
payout.confirmedPayout confirmed on-chain.
payout.failedPayout rejected (insufficient balance, bad address, compliance).
withdrawal.requestedMerchant withdrawal request submitted.
withdrawal.approvedWithdrawal approved by ZyndPay.
withdrawal.broadcastWithdrawal signed and broadcast to TRON.
withdrawal.confirmedWithdrawal confirmed on-chain.
withdrawal.failedWithdrawal rejected by the gateway or compliance.
refund.createdRefund created — pending approval.
refund.approvedRefund approved.
refund.rejectedRefund request rejected.
refund.completedRefund sent back to the original payer.
refund.failedRefund attempt failed.
subscription.createdRecurring subscription created.
subscription.renewal_initiatedRenewal cycle started.
subscription.renewedRecurring subscription charge succeeded.
subscription.failedRenewal charge failed.
subscription.updatedSubscription details changed.
subscription.pausedSubscription paused.
subscription.resumedSubscription resumed.
subscription.cancelledSubscription cancelled.
conversion.confirmedWallet conversion completed.
conversion.failedWallet conversion failed.
dispute.openedA dispute was opened against a payment.
dispute.resolvedA dispute was resolved.
dispute.rejectedA dispute was rejected.
dispute.escalatedA dispute was escalated to a higher level.
aml.flaggedTransaction flagged by AML screening.
splitpayment.createdMarketplace split-payment created.
Cette page a-t-elle été utile ?