API FlavosoWebhooks et événementsVérifier les signatures
Vérifier les signatures
Chaque livraison porte Flavoso-Signature, un HMAC-SHA256 calculé sur l'horodatage et le corps brut. Code de vérification pour Node.js, Python, PHP, Go et C#.
Le principe
Chaque livraison porte l'en-tête Flavoso-Signature: t=1759579200,v1=5257a8…. t est l'heure en secondes depuis 1970 et v1 un HMAC-SHA256, en hexadécimal minuscule :
- Clé : votre secret de signature, la chaîne entière y compris
whsec_, en UTF-8. - Message :
t, un point, le corps brut. Soit1759579200.{"id":"evt_…",…}.
Vérifiez la signature avant de vous fier au contenu. Vous savez alors que la livraison vient de Flavoso et n'a pas été modifiée en route. C'est le même principe que chez Stripe : si vous l'avez déjà implémenté pour Stripe, vous le connaissez déjà.
Étape par étape
- 1
Prendre le corps brut
Exactement les octets reçus, avant toute analyse JSON.
- 2
Découper l'en-tête
Séparez les parties aux virgules, puis chaque partie au premier
=.test présent une fois,v1une ou deux fois. - 3
Vérifier l'âge
Si
ts'écarte de votre horloge de plus de 300 secondes, refusez la livraison. Sinon, une livraison interceptée pourrait être rejouée plus tard. - 4
Calculer la valeur attendue
HMAC-SHA256 avec le secret sur
t+.+ corps, en hexadécimal. - 5
Comparer
Comparez avec chaque
v1en temps constant. Si l'un correspond, la livraison est authentique.
Code de vérification
// flavoso-signature.js
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the body exactly as received. Never JSON.stringify(req.body) again.
export function verifyFlavosoSignature(rawBody, header, secret, toleranceSeconds = 300) {
const parts = header.split(",").map((part) => part.trim().split("="));
const t = Number(parts.find(([key]) => key === "t")?.[1]);
const signatures = parts.filter(([key]) => key === "v1").map(([, value]) => value);
if (!Number.isInteger(t) || signatures.length === 0) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`, "utf8").digest("hex");
return signatures.some(
(s) => s.length === expected.length && timingSafeEqual(Buffer.from(s), Buffer.from(expected)),
);
}Obtenir le corps brut
La plupart des frameworks analysent le JSON d'eux-mêmes. Si vous retransformez ensuite le corps en texte, les caractères changent (espaces, ordre, caractères accentués) et la signature ne correspond plus. Voici comment obtenir les octets :
| Environnement | Corps brut |
|---|---|
| Express | express.raw({ type: "application/json" }) sur cette route |
| Next.js (App Router) | await request.text() |
| Flask | request.get_data() |
| FastAPI | await request.body() |
| Django | request.body |
| PHP | file_get_contents('php://input') |
| Laravel | $request->getContent() |
| Go | io.ReadAll(r.Body) |
| ASP.NET Core | Un StreamReader sur Request.Body, avant tout model binding |
// app/api/flavoso/webhook/route.js (App Router)
import { verifyFlavosoSignature } from "@/lib/flavoso-signature";
export async function POST(request) {
const raw = await request.text(); // exactly as received, before any JSON parsing
const header = request.headers.get("flavoso-signature") ?? "";
if (!verifyFlavosoSignature(raw, header, process.env.FLAVOSO_WEBHOOK_SECRET)) {
return new Response("invalid signature", { status: 400 });
}
await enqueue(JSON.parse(raw)); // answer fast, work in the background
return new Response(null, { status: 200 });
}Vecteur de test
Ce vecteur vous permet de tester votre vérification sans Flavoso : avec ce secret, ce corps et t comme « maintenant », elle doit accepter la signature, et la refuser dès qu'un caractère du corps est modifié.
# Your verification must accept this example with t = 1759579200 as "now".
# openssl prints the same v1 (after "SHA2-256(stdin)="):
printf '%s' '1759579200.{"id":"evt_test","type":"ping"}' \
| openssl dgst -sha256 -hmac 'whsec_test_secret'
# secret: whsec_test_secret
# body: {"id":"evt_test","type":"ping"}
# header: Flavoso-Signature: t=1759579200,v1=d5a5a8b08d9a25070a9e22c9fa06ad59b4082eb039b25cee3d83319e00d8201aChanger de secret
Après « Créer un nouveau secret de signature », Flavoso signe pendant 24 heures à la fois avec le nouveau et avec l'ancien secret, le nouveau en premier : l'en-tête porte alors deux v1. Il suffit que l'un corresponde. Déployez le nouveau secret pendant ce délai ; ensuite, seul le nouveau est valable.
Erreurs fréquentes
- Le corps a été analysé comme du JSON puis retransformé en texte.
- Le mauvais secret : le webhook de votre application et les webhooks propres à un établissement ont des secrets différents.
- L'horloge du serveur est déréglée. Synchronisez-la avec NTP.
- Comparaison avec un hexadécimal en majuscules, ou espaces non supprimés autour des parties de l'en-tête.
Flavoso-Event-Idconsidéré comme une preuve. L'identifiant n'a rien de secret ; seule la signature fait foi.
Une question reste ouverte ? Écrivez à support@flavoso.com en indiquant l'identifiant de X-Request-Id. Il nous permet de retrouver votre requête dans le journal.