Signature

Chaque livraison porte deux headers et un body signé :
X-IziPay-Signature: sha256=<hex>
X-IziPay-Timestamp: 1731843299
{
  "event": "payment_intent.completed",
  "timestamp": 1731843299,
  "data": { "intentId": "pi_...", "merchantId": "...", ... }
}
Le Signature est HMAC-SHA256(secret, rawBody) et le timestamp est lu dans le body signé (champ timestamp) pour bloquer les replays. Toujours vérifier la signature avant de traiter, par exemple avec le SDK :
import { IziPayClient } from 'izichangepay-sdk';

const event = IziPayClient.validateWebhook(
  rawBody,                                  // Buffer brut, PAS du JSON parsé
  req.headers['x-izipay-signature'],
  process.env.IZIPAY_WEBHOOK_SECRET,
);
La procédure complète (vérification en temps constant, anti-replay, exemples Python/PHP sans SDK, rotation de secret, erreurs courantes) est détaillée sur la page dédiée : Signature des webhooks.

Événements

Quelques événements parmi les plus courants :
ÉvénementDéclenché quand
payment_intent.completedL’intention atteint le seuil de payins et passe completed
payment_intent.expiredexpiresAt dépassé sans paiement suffisant
payout.confirmedTransaction blockchain confirmée (avec txHash)
settlement.completedLe reversement (settlement) est confirmé
Le catalogue complet (chaque type, sa condition de déclenchement et son payload) est sur la page Événements webhook.

Retry policy

TentativeDélai après précédente
#1immédiat
#2+1 min
#3+5 min
#4+30 min
#5+2 h
#6+24 h
Critères d’échec : réponse non-2xx ou erreur de transport (timeout 10s, connexion refusée) ; les 3xx sont suivis automatiquement. Après 5 échecs consécutifs : endpoint désactivé, email au propriétaire, livraisons suspendues jusqu’à réactivation manuelle.

Idempotence côté receveur

IzichangePay peut livrer le même événement plusieurs fois (network split, retry après un timeout côté votre serveur). Votre handler doit être idempotent :
async function handleWebhook(event: WebhookEvent) {
  // Persist un "deliveryId" + statut dans votre DB AVANT de traiter
  const eventKey = `${event.type}:${event.data.intentId ?? event.data.payoutId}:${event.timestamp}`;
  const inserted = await db.processedEvents.insertIgnoreDuplicate(eventKey);
  if (!inserted) {
    return; // déjà traité, dédup
  }

  switch (event.type) {
    case 'payment_intent.completed':
      await markOrderPaid(event.data.merchantReference);
      break;
    // ...
  }
}

Acquittement

Répondez 200-299 pour acquitter. Tout autre statut OU timeout → retry. Acquittez rapidement (< 5 secondes). Si votre handler est lent (envoi d’emails, mise à jour de stocks externes), faites-le en async :
app.post('/webhooks/izipay', async (req, res) => {
  const event = validateWebhook(...);
  queue.add('process-izipay-event', event);  // job async
  res.json({ received: true });              // ack immédiat
});

Debug

Dashboard → Développeurs → Webhooks → Historique de livraison : chaque tentative est tracée avec :
  • Code HTTP retourné
  • Body de votre réponse (5000 premiers chars)
  • Headers de votre réponse
  • Durée
  • Erreur réseau le cas échéant
Cliquez Renvoyer pour relancer manuellement une livraison (utile pour rejouer un événement après avoir fixé un bug).
Ne jamais utiliser le event.timestamp pour décider quoi faire (par ex “si > X heures, ignorer”). Utilisez event.data.* (les données de la ressource) et croisez avec votre DB pour décider de l’action. IzichangePay peut envoyer un événement vieux après une coupure.