vendredi 25 septembre 2026

À propos

Contact

E-commerce

Un webhook signé rejeté à tort : déboguer la vérification HMAC de WooCommerce

Un système tiers rejetait un webhook WooCommerce parfaitement légitime pour signature invalide. Le diagnostic a mené à un problème d'encodage bien plus subtil qu'un secret mal copié.

Par Clément Hadrot • 20 février 2024 • 5 min de lecture • Aucun commentaire
Un webhook signé rejeté à tort : déboguer la vérification HMAC de WooCommerce

Le ticket disait simplement : « les webhooks de commande sont rejetés côté ERP depuis ce matin, signature invalide ». Rien n’avait pourtant changé côté configuration WooCommerce, ni le secret partagé, ni l’URL cible. Le genre de symptôme qui pousse instinctivement à soupçonner un problème réseau ou une horloge désynchronisée, alors que la cause était ailleurs.

Ce cas est intéressant précisément parce qu’il illustre un piège classique de la vérification HMAC : la signature ne protège pas un webhook contre une modification malveillante, elle protège contre toute modification, y compris celles que l’on introduit soi-même sans s’en rendre compte entre la réception et la vérification.

Symptôme : un rejet systématique, mais pas total

Premier élément troublant relevé dans les journaux du système tiers : environ 40 % des webhooks passaient sans problème, les autres étaient rejetés pour signature invalide. Pas de corrélation évidente avec l’heure d’envoi ni avec le type d’événement. En revanche, tous les webhooks rejetés partageaient un point commun une fois les commandes concernées ouvertes une à une : leur commentaire de commande ou leur adresse de livraison contenait un caractère accentué ou une apostrophe typographique.

Ce détail a immédiatement réorienté l’hypothèse vers un problème d’encodage plutôt que vers le secret partagé, d’autant que ce dernier n’avait pas été modifié récemment et fonctionnait pour les webhooks « simples ».

Diagnostic : où WooCommerce calcule réellement la signature

WooCommerce calcule la signature HMAC-SHA256 du corps JSON dans WC_Webhook::generate_signature(), à partir de la chaîne de caractères déjà sérialisée en JSON via wp_json_encode(). Le point critique : cette signature porte sur les octets exacts du JSON tel qu’envoyé, pas sur une représentation abstraite des données. Toute différence d’encodage entre la génération et la vérification, même invisible à l’œil nu, invalide la comparaison.

$payload = wp_json_encode( $webhook_payload );
$secret  = $webhook->get_secret();
$signature = base64_encode( hash_hmac( 'sha256', $payload, $secret, true ) );
L'essentiel à retenir : Le secret n'était pas en cause, contrairement à la première hypothèse ; Le corps de la requête était modifié avant la vérification ; Un middleware de logging suffisait à casser la signature

La cause réelle : un proxy applicatif qui reformatait le JSON

L’infrastructure du client faisait transiter les webhooks entrants par un petit proxy de journalisation avant l’ERP, chargé de logguer chaque requête pour audit. Ce proxy, écrit en Node.js, parsait le corps JSON reçu puis le re-sérialisait avant de le transmettre à l’ERP — une pratique courante pour normaliser un format, mais désastreuse pour une vérification de signature.

Le re-encodage JSON de Node.js échappait certains caractères Unicode différemment de wp_json_encode() côté PHP : un guillemet typographique ou une apostrophe issue d’une adresse de livraison n’était pas représenté par la même séquence d’octets après ce passage. La signature, calculée par WooCommerce sur le corps original, ne correspondait donc plus au corps effectivement reçu par l’ERP après reformattage, alors même que les données, elles, restaient identiques d’un point de vue métier.

Le correctif : vérifier avant toute transformation

La solution ne portait pas sur WooCommerce, entièrement conforme, mais sur le proxy : la vérification de signature devait s’exécuter sur le corps brut de la requête, avant tout parsing JSON, en conservant le buffer d’origine plutôt que de retravailler l’objet désérialisé.

  1. Capturer le corps brut de la requête dans le proxy, sans le parser d’abord
  2. Calculer la signature HMAC sur ce corps brut exact
  3. Comparer avec l’en-tête X-WC-Webhook-Signature avant toute autre étape
  4. Ne parser le JSON qu’une fois la vérification validée

Prévention pour les prochaines intégrations

Le principe général à retenir dépasse ce seul cas : dans toute chaîne comportant un proxy, un WAF applicatif ou un middleware de journalisation, la vérification de signature doit toujours intervenir sur les octets les plus proches possible de la réception réseau, avant toute réécriture, même anodine en apparence.

Un corps JSON « équivalent » n’est pas un corps JSON identique. La cryptographie ne raisonne jamais sur le sens des données, seulement sur leurs octets exacts.

Ce qu’il faut vérifier en premier la prochaine fois

  • Le taux d’échec est-il total ou partiel ? Un taux partiel oriente vers un contenu spécifique, pas vers le secret
  • Existe-t-il un composant intermédiaire entre WooCommerce et le point de vérification ?
  • Ce composant reformate-t-il, journalise-t-il ou transforme-t-il le corps de la requête ?

En résumé

Un rejet de signature HMAC n’est pas toujours un problème de secret partagé mal copié : dès qu’un composant intermédiaire retouche le corps de la requête, même sans en changer le sens, la vérification échoue. Face à un taux d’échec partiel et corrélé à un type de contenu précis, chercher du côté de l’encodage avant de suspecter la configuration est souvent le chemin le plus rapide vers la cause réelle.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi