Validateur de signature webhook
Votre fournisseur envoie un header de signature, votre code dit qu'il ne matche pas, et la doc répond par un SDK à installer. Ça ne vous dit pas pourquoi. Collez la signature, votre secret et le corps brut ci-dessous : on recalcule le HMAC dans votre navigateur et, en cas d'échec, on rejoue les quelques mutations qui en sont généralement la cause — une espace parasite dans le secret, un saut de ligne final, un corps resérialisé, un timestamp périmé.
En bref : une signature webhook échoue presque toujours pour l'une de cinq raisons — mauvais secret (test vs live), un corps resérialisé par votre framework, un timestamp hors de la fenêtre de rejeu de 5 minutes, un saut de ligne final, ou un préfixe sha256= / v0= laissé en place. Cet outil recalcule le HMAC et vous dit laquelle.
Collez une signature qui échoue
Les quatre schémas de signature
Les quatre sont du HMAC, mais ils diffèrent sur ce qui est haché et sur l'encodage du résultat. Un seul détail faux et chaque livraison échoue.
| Fournisseur | Ce qui est signé | Algorithme |
|---|---|---|
| Stripe | HMAC de t.body ; signature et timestamp dans un seul header Stripe-Signature | SHA-256 hex |
| GitHub | HMAC du corps brut ; signature dans X-Hub-Signature-256 | SHA-256 hex |
| Shopify | HMAC du corps brut ; signature dans X-Shopify-Hmac-Sha256 | SHA-256 base64 |
| Slack | HMAC de v0:timestamp:body ; le header timestamp est aussi requis | SHA-256 hex |
Twilio fait exception — il hache une URL canonique plus des paramètres triés, pas le corps. Utilisez le validateur de signature Twilio pour ça.
Les cinq vraies causes
Classées par fréquence dans les fils de support.
| # | Cause | Symptôme | Correction |
|---|---|---|---|
| 1 | Mauvais secret | Toutes les livraisons échouent, même une neuve | Confusion test vs live — chaque fournisseur émet un secret de signature différent par mode et par endpoint. |
| 2 | Corps resérialisé | Échoue après que votre framework a parsé le JSON | Signez les octets bruts, pas JSON.stringify(parsed) : l'espacement et l'ordre des clés changent le hash. |
| 3 | Timestamp périmé | Passe en test, échoue sur des événements rejoués | Stripe et Slack rejettent au-delà de 5 minutes. Ne rejouez pas un événement capturé des heures plus tard. |
| 4 | Saut de ligne final | Corps décalé d'un octet, signature jamais bonne | Un proxy ou un éditeur a ajouté un saut de ligne. L'outil signale ce cas précis. |
| 5 | Préfixe comparé tel quel | sha256= ou v0= inclus dans la comparaison | Comparez seulement la partie hex/base64, pas le préfixe de schéma. |
Le piège du corps brut
La cause n° 1 : vous vérifiez contre le corps parsé-puis-resérialisé au lieu des octets exacts. JSON.stringify(JSON.parse(body)) change l'espacement et l'ordre des clés, donc le HMAC ne matche plus. Lisez le corps brut avant tout parsing.
// Express: keep the RAW bytes, do NOT let JSON re-serialise them
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
const raw = req.body; // Buffer, exactly as received
verify(raw, req.headers['stripe-signature'], secret);
});# Flask: request.get_data() returns the raw body; request.json does NOT raw = request.get_data() # bytes, untouched verify(raw, request.headers['X-Hub-Signature-256'], secret)
// Next.js App Router: read the text before parsing
export async function POST(req) {
const raw = await req.text(); // sign THIS, not JSON.stringify(await req.json())
verify(raw, req.headers.get('stripe-signature'), secret);
}Règle simple : capturez la signature et les octets bruts en entrée, vérifiez, puis parsez.
Timestamps et rejeu
Stripe et Slack intègrent un timestamp dans la chaîne signée et rejettent tout ce qui dépasse cinq minutes — c'est une protection anti-rejeu, pas un bug. GitHub et Shopify non, donc une signature GitHub valide le reste à vie. Si l'outil affiche valide mais expiré, votre secret et votre corps sont bons ; vous rejouez juste un vieil événement. Regénérez une livraison fraîche plutôt que de renvoyer une capturée.
Une fois la signature valide
Besoin d'envoyer un événement de test correctement signé à votre endpoint ? Le signeur de webhook en construit un pour les six fournisseurs. Pour capturer et inspecter de vraies livraisons, pointez-les vers un tunnel Relay.
Questions fréquentes
Stockez-vous mon secret de signature ?
Non. Le HMAC est calculé dans votre navigateur avec Web Crypto ; le secret et le corps n'atteignent jamais nos serveurs. Ouvrez l'onglet Réseau : aucune requête ne part quand vous cliquez sur Vérifier.
Quels fournisseurs sont supportés ?
Stripe, GitHub, Shopify et Slack — les quatre schémas HMAC sur le corps. Twilio signe une URL canonique à la place, il a donc son propre outil : le validateur de signature Twilio.
Ça passe ici mais mon serveur rejette quand même. Pourquoi ?
Presque toujours le corps. Votre framework a parsé le JSON et vous l'avez resérialisé avant de vérifier, ce qui change l'espacement et l'ordre des clés. Signez les octets bruts — voir les extraits Express, Flask et Next ci-dessus.
Que veut dire « valide mais expiré » ?
Le HMAC est correct, mais Stripe et Slack refusent aussi les signatures de plus de cinq minutes pour bloquer le rejeu. Si vous avez capturé un événement et le renvoyez plus tard, la vérification échoue sur le timestamp même si le secret est bon.