WebhookToolkit
Stripe · GitHub · Shopify · Slack

Validateur de signature webhook

Dernière mise à jour : juillet 2026

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

Tout s'exécute dans votre navigateur avec l'API Web Crypto. Votre secret de signature n'est jamais envoyé à nos serveurs — vérifiez-le dans l'onglet Réseau.

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.

FournisseurCe qui est signéAlgorithme
StripeHMAC de t.body ; signature et timestamp dans un seul header Stripe-SignatureSHA-256 hex
GitHubHMAC du corps brut ; signature dans X-Hub-Signature-256SHA-256 hex
ShopifyHMAC du corps brut ; signature dans X-Shopify-Hmac-Sha256SHA-256 base64
SlackHMAC de v0:timestamp:body ; le header timestamp est aussi requisSHA-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.

#CauseSymptômeCorrection
1Mauvais secretToutes les livraisons échouent, même une neuveConfusion test vs live — chaque fournisseur émet un secret de signature différent par mode et par endpoint.
2Corps resérialiséÉchoue après que votre framework a parsé le JSONSignez les octets bruts, pas JSON.stringify(parsed) : l'espacement et l'ordre des clés changent le hash.
3Timestamp périméPasse en test, échoue sur des événements rejouésStripe et Slack rejettent au-delà de 5 minutes. Ne rejouez pas un événement capturé des heures plus tard.
4Saut de ligne finalCorps décalé d'un octet, signature jamais bonneUn proxy ou un éditeur a ajouté un saut de ligne. L'outil signale ce cas précis.
5Préfixe comparé tel quelsha256= ou v0= inclus dans la comparaisonComparez 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
// 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
# 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
// 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.

Ouvrir le signeurValidateur signature TwilioRelay

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.

Validateur de signature webhook : Stripe, GitHub, Shopify, Slack · Webhook Toolkit