Configuration, signature HMAC et événements marchands.
Créez un endpoint dans le menu Développeurs, section Webhooks, ou via POST /merchant/merchants/:id/webhook-endpoints.
Le secret whsec_… n'est affiché qu'une fois. Pour faire tourner le secret sans recréer l'URL : POST …/webhook-endpoints/:endpointId/rotate-secret.
Chaque livraison inclut X-Izzi-Signature (HMAC-SHA256, hex sans préfixe) et X-Izzi-Timestamp (secondes Unix).
Corps signé : concaténation {timestamp}.{rawBody} (point littéral entre les deux).
Fenêtre de validité recommandée : 300 secondes. Vérifiez la signature avant de parser le JSON.
En-tête X-Izzi-Event-Id : identifiant unique de la livraison (idempotence côté récepteur).
Il n'y a pas d'endpoint IzziPay pour vérifier une livraison. Recalculez le HMAC chez vous avec le secret whsec_… affiché à la création de l'endpoint.
Le SDK Node expose verifyWebhookSignature. Il applique la fenêtre de 300 secondes et une comparaison en temps constant.
import { verifyWebhookSignature } from "@izzipay/sdk";
const result = verifyWebhookSignature({
secret: process.env.IZZI_WEBHOOK_SECRET!,
rawBody: requestBody,
signature: request.headers["x-izzi-signature"],
timestamp: request.headers["x-izzi-timestamp"],
});
if (!result.verified) {
throw new Error(result.reason ?? "invalid signature");
}Même calcul sans SDK, avec node:crypto.
Passez le corps brut, octet pour octet. Ne reconstruisez pas le JSON avec JSON.stringify(req.body) après un middleware qui parse le body.
Sur Express, utilisez express.raw() sur cette route. Sur Fastify, conservez le raw body dans preParsing.
En Python, comparez le même message signé avec hmac.compare_digest.
import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECONDS = 300;
function verifyIzziWebhook(input: {
secret: string;
rawBody: string;
signature: string | undefined;
timestampHeader: string | undefined;
}): boolean {
if (!input.signature || !input.timestampHeader) return false;
const timestamp = Number(input.timestampHeader);
if (!Number.isFinite(timestamp)) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) return false;
const expected = createHmac("sha256", input.secret)
.update(`${timestamp}.${input.rawBody}`)
.digest("hex");
if (expected.length !== input.signature.length) return false;
return timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(input.signature, "hex"),
);
}Jusqu'à 20 tentatives par événement, backoff exponentiel (1 s → 1 h), timeout HTTP 8 s par tentative.
Circuit breaker après échecs répétés sur un endpoint ; consultez l'historique via GET …/webhook-deliveries.
IZZIPAY ne publie pas de liste fixe d'IP egress pour les webhooks. Authentifiez chaque requête via la signature HMAC plutôt que par filtrage IP.
Voir la référence API Webhooks pour les payloads exemples de chaque type.