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énement | Déclenché quand |
|---|
payment_intent.completed | L’intention atteint le seuil de payins et passe completed |
payment_intent.expired | expiresAt dépassé sans paiement suffisant |
payout.confirmed | Transaction blockchain confirmée (avec txHash) |
settlement.completed | Le 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
| Tentative | Délai après précédente |
|---|
| #1 | immé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.