Aller au contenu

Vérifier la signature

Chaque webhook est signé avec votre clé secrète. Vérifiez la signature avant de traiter quoi que ce soit : sans cela, n’importe qui connaissant votre URL pourrait vous faire croire à un paiement.

En-tête Contenu
X-Afriflow-Timestamp Horodatage Unix (secondes) de l’envoi
X-Afriflow-Signature t=<horodatage>,v1=<HMAC-SHA256 en hexadécimal>
  1. Récupérez le corps de la requête brut, exactement tel que reçu (pas le JSON reparsé puis re-sérialisé).

  2. Concaténez <horodatage>.<corps brut>.

  3. Calculez le HMAC-SHA256 de cette chaîne avec votre clé secrète af_live_sec_....

  4. Comparez en temps constant avec la valeur v1 de l’en-tête. Refusez si l’horodatage a plus de 5 minutes (protection contre le rejeu).

Express
import crypto from "node:crypto";
import express from "express";
const app = express();
function verifierSignature(corpsBrut, entetes, secret) {
const ts = entetes["x-afriflow-timestamp"];
const sig = (entetes["x-afriflow-signature"] || "")
.split(",").find((p) => p.startsWith("v1="))?.slice(3);
if (!ts || !sig) return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5 minutes
const attendu = crypto.createHmac("sha256", secret).update(`${ts}.${corpsBrut}`).digest("hex");
return attendu.length === sig.length
&& crypto.timingSafeEqual(Buffer.from(attendu), Buffer.from(sig));
}
// Le corps doit rester BRUT : express.raw, pas express.json
app.post("/webhooks/cartflox", express.raw({ type: "application/json" }), (req, res) => {
const brut = req.body.toString("utf8");
if (!verifierSignature(brut, req.headers, process.env.CARTFLOX_SECRET_KEY)) {
return res.status(400).send("signature invalide");
}
const evt = JSON.parse(brut);
if (evt.event === "payment.completed") {
// Marquez la commande evt.data.metadata.order_id comme payée.
// Rendez ce traitement idempotent : le même événement peut arriver deux fois.
}
res.sendStatus(200); // répondez vite, traitez le reste en tâche de fond
});