Vérifier la signature d’un webhook avant tout traitement est non-négociable. Sans vérification, n’importe qui peut envoyer un faux payment_intent.completed à votre endpoint et marquer une commande comme payée sans qu’aucun fonds n’ait été reçu.
Le contrat
Signature : HMAC-SHA256(secret, raw_body) en hex
Timestamp : aussi à l’intérieur du body signé
Pourquoi le timestamp est important
Sans vérification de timestamp, un attaquant qui capture un webhook valide peut le rejouer indéfiniment. Le timestamp inclus dans le body signé :
- Bloque les replays > 5 minutes
- Permet à votre serveur de détecter si le timestamp est trop loin dans le futur (clock skew exploitable)
Avec le SDK Node (recommandé)
Le SDK :
- Compare la signature en temps constant (
crypto.timingSafeEqual)
- Rejette les headers multi-value (header
Signature dupliqué → ambigu → reject)
- Tolérance par défaut 5 min (configurable via
toleranceSeconds)
- Rejette les timestamps > 60s dans le futur
Sans SDK (autre langage)
Erreurs courantes
Ne JAMAIS désactiver la vérification “temporairement pour debug”. Si vous ne pouvez pas faire passer un webhook, comparez en local avec un test depuis le dashboard (Webhooks → Envoyer test). Le payload est identique à un événement réel.
Changer de secret
Le secret d’un endpoint est généré une seule fois, à sa création, et n’est jamais régénéré : il n’existe pas de rotation in-place ni de bouton « Rotate secret ». Pour utiliser un nouveau secret, créez un nouvel endpoint (vous recevez un nouveau whsec_…), puis supprimez l’ancien.
Pour basculer sans perdre d’événement, faites se chevaucher les deux endpoints le temps de la transition. Tant que les deux sont actifs, votre serveur reçoit chaque événement deux fois (une fois par secret) : dédupliquez via l’id de l’événement.
- Créez un nouvel endpoint (vous pouvez réutiliser la même URL) et notez son secret
NEW_SECRET.
- Mettez votre serveur à jour pour accepter les deux secrets (ci-dessus).
- Supprimez l’ancien endpoint, puis retirez
OLD_SECRET.