Comparer un en-tête HTTP déclaré à une signature cryptographique vérifiée, c’est comparer une carte de visite posée sur un bureau à une pièce d’identité contrôlée par un tiers de confiance. L’une se fabrique en un instant, l’autre repose sur un secret partagé impossible à deviner. Un audit d’intégration a mis en évidence une confusion exacte entre ces deux niveaux de confiance.
Le point d’entrée REST recevant les webhooks vérifiait la présence d’un en-tête personnalisé, X-Origine-Plateforme, censé confirmer que la requête provenait bien du service partenaire attendu. Aucune signature, aucun secret partagé, aucune vérification cryptographique n’accompagnait ce contrôle.
Ce qu’on observe dans le code
Le gestionnaire de la route REST se limitait à ceci :
register_rest_route( 'integration/v1', '/webhook', array(
'methods' => 'POST',
'callback' => 'traiter_webhook_partenaire',
'permission_callback' => function ( $request ) {
return 'PartenaireCommandes' === $request->get_header( 'x_origine_plateforme' );
},
) );
Le contrôle d’accès reposait entièrement sur la comparaison d’une chaîne de caractères transmise par l’appelant lui-même, dans un en-tête qu’il définit à sa guise. Rien, dans le protocole HTTP, n’empêche un client quelconque de définir cet en-tête à la valeur souhaitée avant d’envoyer sa requête.
Pourquoi c’est un problème
Un en-tête HTTP n’est jamais une preuve d’identité : il s’agit d’une information que l’appelant choisit librement de transmettre, exactement comme le corps de la requête. Le confondre avec un mécanisme d’authentification revient à ouvrir une porte sur la seule foi de ce que la personne qui frappe déclare être.
curl -X POST https://exemple.fr/wp-json/integration/v1/webhook \
-H "X-Origine-Plateforme: PartenaireCommandes" \
-H "Content-Type: application/json" \
-d '{"commande_id": 4821, "statut": "remboursee"}'
Cette seule commande, exécutable depuis n’importe quel poste sans le moindre secret, suffisait à faire passer une requête forgée pour un webhook légitime. Le traitement associé modifiait directement le statut d’une commande en base, avec les conséquences comptables que cela implique.

Ce que révèle l’écart entre en-tête et signature
Une signature HMAC repose sur un secret partagé, connu uniquement des deux parties, utilisé pour calculer une empreinte du corps de la requête. Cette empreinte ne peut être reproduite sans connaître le secret, contrairement à un en-tête dont la valeur se lit et se copie à l’œil nu dans n’importe quel outil d’inspection réseau.
| Critère | En-tête déclaré | Signature HMAC vérifiée |
|---|---|---|
| Qui définit la valeur | L’appelant, librement | Calculée à partir d’un secret partagé |
| Reproductible sans secret | Oui, immédiatement | Non, cryptographiquement impossible |
| Protège contre le rejeu | Non | Oui, si un horodatage est inclus |
Quoi faire à la place
Le principe à retenir n’est pas la mécanique complète d’une vérification HMAC, déjà largement documentée par ailleurs, mais la hiérarchie de confiance à respecter : un en-tête peut servir d’indication de routage ou de contexte, jamais de preuve d’authenticité. Toute décision qui modifie une donnée métier doit s’appuyer sur une preuve que l’appelant ne peut pas fabriquer seul.
- Ne jamais utiliser un en-tête arbitraire comme unique critère de
permission_callback. - Exiger une signature calculée à partir d’un secret connu des deux parties, jamais transmis en clair.
- Rejeter toute requête sans signature valide, y compris pendant une phase de migration ou de test.
Repère retenu de cet audit : si un attaquant peut reproduire un contrôle d’accès avec une seule ligne de commande, ce contrôle n’en est pas un.
En résumé
La confusion entre déclaration et preuve reste l’un des antipatterns les plus discrets d’une intégration webhook, précisément parce que le code correspondant compile, fonctionne en test, et semble raisonnable à première lecture. Elle ne se révèle qu’au moment où quelqu’un se donne la peine de reproduire la requête depuis l’extérieur, sans disposer d’aucun secret.