Les pubs vous déplaisent ? Aller Sans pub Auj.

HMAC — Comment les webhooks savent que vous n'êtes pas en train de mentir

Publié le

Tout serveur peut envoyer une requête POST à votre point d'entrée de webhook. Les signatures HMAC permettent aux expéditeurs légitimes de prouver qu'ils ont rédigé le contenu — et vous permettent de le vérifier.

HMAC — Comment les webhooks savent que vous ne mentez pas 1
ANNONCE · Supprimer ?

Chaque fois que Stripe charge une carte, il déclenche un webhook. Chaque fois que GitHub fusionne une demande de pull, il déclenche un webhook. Ces plateformes envoient des requêtes POST vers une URL que vous leur avez fournie — mais voici la vérité inquiétante : n'importe qui peut envoyer une requête POST vers cette même URL.

Alors, comment votre serveur sait que la requête provient vraiment de Stripe et non d'un attaquant qui a deviné l'URL de votre point d'entrée ? La réponse est l'HMAC — et une fois que vous l'avez comprise, vous comprendrez pourquoi c'est l'approche standard sur toutes les plateformes sérieuses d'API.

Le problème : n'importe qui peut envoyer un POST à votre point d'entrée

Les points d'entrée de webhook sont simplement des URLs. Elles sont publiquement accessibles (elles le doivent être, pour que l'expéditeur puisse les atteindre), et elles acceptent des requêtes POST. Il n'y a rien qui empêche un acteur malveillant de créer un payload faux et de l'envoyer à votre point d'entrée.

Imaginons que votre gestionnaire de webhook fait ceci lorsqu'il reçoit un événement :

if event["type"] == "payment.completed":
    fulfill_order(event["data"]["order_id"])

Un attaquant qui connaît votre URL de point d'entrée pourrait envoyer un faux payment.completed événement avec n'importe quel ID de commande qu'il souhaite. Sans vérification, votre serveur accepterait des commandes qui n'avaient jamais été payées.

Vous avez besoin d'une manière de vérifier que le payload a été écrit par quelqu'un qui possède un secret que vous partagez — sans transmettre ce secret dans la requête.

Qu'est-ce que l'HMAC ?

HMAC signifie Code d'authentification basé sur le hachage. C'est une construction qui combine une fonction de hachage cryptographique (généralement SHA-256) avec une clé secrète pour produire une signature. Cette signature prouve deux choses :

  • Authenticité — le message a été créé par quelqu'un qui possède la clé secrète
  • Intégrité — le message n'a pas été modifié pendant le transit

La propriété clé de l'HMAC : vous ne pouvez pas produire une signature valide sans connaître la clé secrète. Et vous ne pouvez pas inverser la signature pour récupérer la clé. C'est une preuve unidirectionnelle.

L'HMAC par rapport à une simple hachage

Un simple hachage (comme SHA-256) du payload résout le problème d'intégrité, mais pas l'authenticité. Un attaquant qui intercepte un payload valide pourrait recalculer le hachage d'un payload modifié. L'HMAC mélange votre clé secrète dans chaque étape du processus de hachage, donc sans la clé, vous ne pouvez pas produire une signature correspondante même si vous connaissez l'algorithme de hachage utilisé.

Comment fonctionne l'HMAC des webhooks

Le processus comporte trois étapes : échange de clé, signature et vérification.

Étape 1 : Échange de clé (se produit une fois)

Lorsque vous configurez un webhook avec une plateforme comme Stripe ou GitHub, elles génèrent un secret de webhook et vous le montrent une seule fois. Vous le stockez côté serveur (jamais dans du code client ou dans des dépôts publics). C'est tout — la clé ne se transmet plus jamais sur le réseau.

Étape 2 : L'expéditeur signe le payload

Avant d'envoyer le webhook, la plateforme calcule une signature HMAC sur le corps brut de la requête en utilisant votre clé partagée :

signature = HMAC-SHA256(secret_key, request_body)

La signature est ensuite attachée à la requête, généralement dans une en-tête comme X-Hub-Signature-256 (GitHub) ou Stripe-Signature (Stripe). Le payload brut voyage dans le corps sans changement.

Étape 3 : Vous vérifiez à la réception

Lorsque votre serveur reçoit le webhook, vous recalculer la même HMAC à partir du corps brut et de votre clé stockée, puis vous comparez le résultat à la signature dans l'en-tête. Si elles correspondent, le payload est authentique et non modifié. Si elles ne correspondent pas, vous le rejetez.

expected = HMAC-SHA256(your_secret, raw_body)
if not constant_time_equal(expected, header_signature):
    return 401

Avis comparaison en temps constant — nous reviendrons sur pourquoi cela compte.

Exemples concrets

Stripe

Stripe envoie une Stripe-Signature en-tête contenant une date et une ou plusieurs signatures :

Stripe-Signature: t=1679000000,v1=abc123...,v0=oldformat...

La signature est calculée sur timestamp.payload (concaténée avec un point). L'inclusion de la date permet à Stripe de se protéger contre les attaques de répétition — si un attaquant capte une requête valide et la réenvoie plus tard, vous pouvez la rejeter car la date est trop ancienne.

GitHub

GitHub envoie une X-Hub-Signature-256 en-tête au format sha256=<hex_digest>. La signature est un HMAC-SHA256 du corps brut en utilisant la clé de webhook que vous avez configurée dans les paramètres de votre dépôt.

Shopify

Shopify utilise une X-Shopify-Hmac-Sha256 en-tête avec une signature HMAC-SHA256 encodée en Base64 — même concept, différent encodage.

Vérification dans le code

Voici comment la vérification se présente dans trois langages courants. Le modèle est identique — seules les appels de bibliothèque varient.

Python

import hmac
import hashlib

def verify_webhook(secret: str, payload: bytes, signature_header: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    received = signature_header.removeprefix("sha256=")
    return hmac.compare_digest(expected, received)

Node.js

const crypto = require('crypto');

function verifyWebhook(secret, rawBody, signatureHeader) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  
  const received = signatureHeader.replace('sha256=', '');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(received)
  );
}

PHP

function verifyWebhook(string $secret, string $rawBody, string $signatureHeader): bool {
    $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $signatureHeader);
}

Des erreurs qui compromettent la sécurité

1. Utilisation de == au lieu d'une comparaison en temps constant

Comparaison standard de chaînes (==, ===) court-circuite dès qu'elle trouve une incohérence. Cela crée un canal de temps: un attaquant peut mesurer combien de temps votre serveur met pour rejeter différentes signatures. Les chaînes partageant un préfixe plus long prennent un peu plus de temps à être rejetées. Avec suffisamment de requêtes, un attaquant peut utiliser cela pour reconstruire une signature valide, byte par byte.

Utilisez toujours une comparaison en temps constant : hmac.compare_digest() en Python, crypto.timingSafeEqual() dans Node.js, hash_equals() dans PHP.

2. Analyse du corps avant la vérification

L'HMAC est calculé sur les octets bruts du corps de la requête. Si vous analysez d'abord le JSON puis que vous le re-sérialisez pour vérifier, vous pourriez obtenir une séquence de caractères différente (ordre de clés différent, espaces, encodage). Capturé toujours le corps brut avant que le parser de votre framework ne l'ait touché, puis vérifiez à partir de ce corps.

3. Pas de vérification des attaques de répétition

Une requête signée est valide pour toujours — sauf si vous vérifiez la date. Si une plateforme inclut une date dans son schéma de signature (Stripe le fait ; GitHub ne le fait pas), rejetez les requêtes où la date est antérieure à quelques minutes. Cela empêche un attaquant de capturer et de répéter une requête légitime.

4. Inclusion de la clé dans le code source

Les clés de webhook doivent être stockées dans des variables d'environnement ou un gestionnaire de secrets, jamais commitées dans le contrôle de version. Une clé perdue signifie qu'un attaquant peut fabriquer n'importe quel payload pour toujours — jusqu'à ce que vous le rotiez.

Ce que l'HMAC ne protège pas

L'HMAC prouve que le payload a été signé par quelqu'un possédant la clé. Il ne pas protège pas contre :

  • Un expéditeur compromis — si l'infrastructure de signature de Stripe était compromise, des événements faux auraient encore des signatures valides
  • Attaques de répétition — sauf si vous validez également une date ou un nonce
  • La confidentialité — l'HMAC ne chiffre rien ; le payload voyage en clair (bien que HTTPS le gère)

Pour la plupart des intégrations de webhooks, l'HMAC sur HTTPS couvre tout ce dont vous avez besoin.

Envie d'une expérience sans pub ? Passez à la version sans pub

Installez nos extensions

Ajoutez des outils IO à votre navigateur préféré pour un accès instantané et une recherche plus rapide

Sur Extension Chrome Sur Extension de bord Sur Extension Firefox Sur Extension de l'opéra

Le Tableau de Bord Est Arrivé !

Tableau de Bord est une façon amusante de suivre vos jeux, toutes les données sont stockées dans votre navigateur. D'autres fonctionnalités arrivent bientôt !

ANNONCE · Supprimer ?
ANNONCE · Supprimer ?
ANNONCE · Supprimer ?

Coin des nouvelles avec points forts techniques

Impliquez-vous

Aidez-nous à continuer à fournir des outils gratuits et précieux

Offre-moi un café
ANNONCE · Supprimer ?