Passer au contenu

Vérifier les webhooks signés de Payora en PHP

Apprenez à récupérer le corps brut d’un webhook Payora et à vérifier sa signature en PHP avant de traiter un événement de paiement.

Payora11 min de lectureEN · RU · UK · ES · DE · FR · IT · PL · PT · HI · ZH
Vérifier les webhooks signés de Payora en PHP

Que sont les webhooks signés de Payora ?

Les webhooks signés de Payora sont des requêtes HTTP accompagnées d’une signature numérique. Votre point de terminaison PHP peut ainsi vérifier que l’événement provient bien de Payora et qu’il n’a pas été modifié en cours de route. Pour savoir comment vérifier un webhook Payora en PHP, le principe est simple : Payora envoie un événement, votre serveur lit le corps brut de la requête, puis votre code vérifie la signature avant de faire confiance à son contenu.

Cette vérification est importante, car n’importe quel client connaissant votre URL peut envoyer un webhook. Un acteur malveillant peut récupérer le point de terminaison dans des journaux, puis envoyer par POST un faux événement de paiement avec un corps JSON bien formé. La signature bloque cette tentative.

L’événement n’est exploitable qu’une fois vérifié. Avant cela, ce n’est que du texte.

Imaginez un ticket numéroté présenté à l’entrée. Le corps de la requête est le message, la signature est le cachet, et votre code PHP vérifie que les deux correspondent. Si c’est le cas, vous traitez l’événement. Sinon, vous le rejetez immédiatement.

Si votre site gère déjà des paiements, la même rigueur s’applique à d’autres éléments de votre infrastructure. Un commerçant qui consulte un guide sur les passerelles de paiement en cryptomonnaie pour le commerce en ligne retrouvera le même principe : accepter moins, vérifier davantage, puis seulement mettre à jour la commande.

Prérequis pour un point de terminaison PHP dédié aux webhooks

Pas besoin d’un framework complexe. Un point de terminaison en PHP 8.x suffit. PHP 7.4 peut aussi convenir à certains projets, mais votre code doit pouvoir accéder au corps brut de la requête et aux en-têtes envoyés par Payora. Votre serveur doit donc autoriser l’accès HTTP habituel, sans couche personnalisée qui réécrit ou supprime les en-têtes.

Utilisez HTTPS. Sans TLS, le webhook circule en clair, et une signature ne compense pas une sécurité de transport insuffisante. Un certificat, une configuration correcte de l’hôte virtuel et une URL publique accessible sont indispensables.

En pratique, deux éléments de PHP sont importants : l’extension hash pour le calcul HMAC et, si vous gérez le JSON, les fonctions JSON intégrées à PHP. cURL peut aussi vous être utile pour effectuer des tests de votre côté, mais le point de terminaison du webhook n’en a pas besoin.

Il vous faut également le secret ou la clé de signature Payora correspondant à la configuration du webhook dans votre compte Payora. Conservez cette valeur en dehors de la racine Web et ne l’ajoutez pas à Git. Un secret exposé dans un dépôt public réduit la vérification à une simple mise en scène.

Dernier point : votre point de terminaison ne doit accepter que les requêtes POST. Les requêtes GET peuvent renvoyer une erreur 405. Cette petite restriction simplifie le dépannage, car elle réduit le trafic à examiner et aide à sécuriser un point de terminaison webhook PHP.

Récupérer le corps brut du webhook et ses en-têtes

La vérification de la signature dépend des octets exacts envoyés par Payora. En PHP, lisez le corps brut de la requête avec file_get_contents('php://input'). Ne reconstruisez pas le contenu à partir de $_POST ou d’un JSON décodé : ces opérations peuvent modifier les espaces, l’ordre des clés ou l’encodage.

La même règle s’applique aux en-têtes. Vous avez besoin de l’en-tête de signature. Utilisez getallheaders() si cette fonction est disponible, ou lisez des variables serveur comme $_SERVER['HTTP_X_...'] si votre environnement ne fournit pas cet utilitaire. De petites différences entre hébergeurs peuvent faire toute la différence.

Imaginez un corps de webhook de 412 octets. Si votre code le décode puis le réencode, vous retrouverez peut-être les mêmes champs, mais les octets ne seront plus identiques. Cette seule différence suffit à faire échouer la vérification.

Gardez le bon réflexe : récupérez d’abord, analysez ensuite. Commencez par stocker la chaîne brute, puis examinez les en-têtes. Ce n’est qu’après que vous devez manipuler la structure du contenu.

Ce réflexe fait aussi gagner du temps lorsque vous testez des paiements avant le lancement. L’article sur la façon de tester un paiement en cryptomonnaie applique le même principe aux événements de test : la fidélité du contenu est essentielle pour que vos tests soient utiles.

Reconstituer la signature attendue en PHP

La méthode habituelle consiste à calculer un HMAC à partir du corps brut et d’un secret partagé. Cette signature HMAC webhook Payora doit être construite avec l’algorithme indiqué dans la documentation des webhooks Payora. Si la documentation mentionne SHA-256, utilisez SHA-256. Si elle précise un préfixe, une balise de version ou un format d’horodatage, respectez exactement ces indications.

En général, on construit le message à signer à partir de l’horodatage et du corps brut, puis on utilise hash_hmac() avec le secret partagé et l’algorithme de hachage indiqué. Selon les spécifications, le résultat devra peut-être être encodé en hexadécimal ou en base64. Ne devinez pas le format : vérifiez-le dans les paramètres du webhook.

Voici un exemple minimal en PHP, avec des valeurs de remplacement pour les éléments qui peuvent varier selon le format de Payora :

$rawBody = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_PAYORA_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_PAYORA_SIGNATURE'] ?? '';
$secret = getenv('PAYORA_WEBHOOK_SECRET');

$signedPayload = $timestamp . '.' . $rawBody;
$expected = hash_hmac('sha256', $signedPayload, $secret);

Cet exemple illustre une structure, mais ne garantit pas qu’elle corresponde au format utilisé. Si Payora signe uniquement le contenu, retirez l’horodatage de la chaîne. Si Payora signe une représentation JSON canonique, suivez précisément cette méthode au lieu d’utiliser celle que vous préférez. Un mauvais séparateur suffira à faire échouer toutes les requêtes.

Pour les commerçants qui s’intéressent aussi aux règles de facturation et aux cas particuliers, l’article sur les limites des factures de paiement en cryptomonnaie peut être utile : le traitement des webhooks et la politique de facturation se rejoignent souvent au cours du cycle de vie d’une commande.

Comparer la signature calculée à celle de Payora

Une fois la signature attendue calculée, comparez-la à la signature reçue au moyen d’une comparaison à temps constant. En PHP, utilisez généralement hash_equals(). Les comparaisons simples avec == ou === ne sont pas adaptées : elles peuvent révéler des informations liées au temps d’exécution et gèrent moins bien certains cas de différences subtiles.

Avant la comparaison, normalisez le format exactement comme le demande Payora. Si l’en-tête est en hexadécimal, comparez des valeurs hexadécimales. S’il est en base64, comparez des valeurs en base64. Si la signature reçue comporte un préfixe tel que sha256=, retirez-le ou conservez-le selon les indications de la documentation.

Voici les étapes à suivre :

  1. Lisez le corps brut de la requête.
  2. Lisez l’en-tête de signature.
  3. Calculez la signature attendue à partir de la formule documentée.
  4. Comparez les deux chaînes avec hash_equals().
  5. Si elles ne correspondent pas, rejetez la requête avec une réponse 401 ou 400.

Une réponse 401 convient si la signature est incorrecte. Une réponse 400 est adaptée si des en-têtes obligatoires sont manquants. Dans les deux cas, la réponse doit rester concise. Ne fournissez aucun indice supplémentaire.

Une seule consigne à retenir : n’affichez jamais le secret. C’est une erreur rare, mais aux conséquences désastreuses.

Gérer l’horodatage, la protection contre les rejeux et les fenêtres de tolérance

Si Payora inclut un horodatage ou un nonce, vérifiez-le avant d’accepter l’événement. Vous bloquerez ainsi les attaques par rejeu, lors desquelles un attaquant renvoie plus tard un webhook valide pour tenter de faire payer ou expédier une commande deux fois. La fenêtre de validité de l’horodatage doit respecter la règle de Payora, et non reposer sur une estimation de votre part.

La méthode la plus courante consiste à comparer l’horodatage du webhook à l’heure de votre serveur et à rejeter les requêtes trop anciennes, en dehors de la fenêtre de tolérance. Si la documentation indique 5 minutes, utilisez 5 minutes. Si elle indique 300 secondes, définissez un paramètre nommé de 300 secondes plutôt que de coder en dur un nombre énigmatique dans trois fichiers.

Un décalage d’horloge peut faire rejeter des requêtes pourtant valides. Un serveur qui retarde de 7 minutes peut refuser un webhook légitime. Synchronisez vos serveurs avec NTP et configurez le fuseau horaire de façon cohérente. Le fuseau horaire ne modifie pas les horodatages Unix, mais une mauvaise configuration locale peut compliquer la lecture des journaux.

Les nonces sont encore plus simples à gérer, lorsqu’ils sont utilisés. Enregistrez chaque nonce une seule fois, marquez-le comme utilisé et refusez toute nouvelle occurrence. Un petit cache ou une table de base de données peut suffire. Un nonce, une seule acceptation.

Cette question de politique se pose aussi si vos pages de paiement proposent des prix fixes. Un article consacré à l’intérêt d’utiliser un prix fixe peut vous aider à harmoniser les règles de taux de change et le traitement des webhooks, afin de ne pas confirmer un montant incorrect après la réception tardive d’un événement.

Traiter les événements valides et renvoyer le bon code HTTP

Une fois la vérification réussie, analysez le contenu et adaptez le traitement au type d’événement. Un événement payment.succeeded ne doit pas suivre le même parcours qu’un événement refund.updated. Séparez clairement ces traitements : chaque gestionnaire aura ainsi une seule responsabilité et vos journaux resteront lisibles.

La logique métier doit s’exécuter avant l’envoi d’une réponse 2xx. Si vous marquez une commande comme payée, enregistrez d’abord les données dans la base de données. Si vous devez envoyer un e-mail, ajoutez-le d’abord à la file d’attente. Si l’une ou l’autre de ces étapes échoue, renvoyez une erreur 500 afin que Payora réessaie plus tard.

Il est important de respecter cet ordre, car une réponse 2xx indique généralement à Payora que l’événement a été accepté. Si vous renvoyez un code 200 trop tôt, puis qu’une erreur survient lors de l’écriture en base de données, Payora et votre application risquent de ne plus être synchronisés. Ce genre de bug coûte cher quand il survient à deux heures du matin.

Une réponse minimale suffit. « OK » convient. Mieux encore, ne renvoyez aucun corps et contentez-vous d’un code d’état explicite. Ne renvoyez pas le contenu de la requête.

Pour les équipes qui facturent aussi leurs clients à partir d’événements de paiement, l’article sur les passerelles de paiement en cryptomonnaie pour les travailleurs indépendants montre comment un événement technique déclenché par un webhook peut donner lieu à une véritable opération de facturation sans ajouter d’étapes inutiles.

Résoudre les problèmes de vérification de signature

La plupart des échecs ont cinq causes : le corps brut a été modifié, le nom de l’en-tête est incorrect, le secret n’est pas le bon, l’encodage diffère ou l’horodatage dépasse la fenêtre de tolérance. Commencez par vérifier le corps. Si vous avez enregistré le contenu brut et la signature calculée, comparez-les avant de toucher à la base de données.

Les modifications du corps sont fréquentes. Un intergiciel de framework peut supprimer des espaces, décoder le JSON ou normaliser les fins de ligne. Un simple retour à la ligne supplémentaire suffit. Si Payora a signé {"a":1} et que votre application lit {"a":1}\n, le HMAC ne correspondra pas.

Vérifiez ensuite les en-têtes. Certains serveurs remplacent les tirets par des traits de soulignement. Certaines couches de proxy suppriment les en-têtes personnalisés, sauf autorisation explicite. Si votre code PHP ne reçoit pas l’en-tête de signature, vérifiez la configuration du serveur Web et du proxy, pas seulement votre code.

Les problèmes d’encodage sont plus difficiles à repérer. L’UTF-8 et les caractères Unicode échappés peuvent sembler identiques dans les journaux tout en produisant des octets différents. Si votre application réécrit le JSON, la signature peut devenir invalide, même si le contenu semble identique dans un navigateur.

Un secret incorrect est la cause la plus simple, et pourtant celle que l’on vérifie souvent en dernier. Contrôlez séparément la variable d’environnement, le secret de préproduction et celui de production. Une clé copiée depuis le mauvais compte peut vous faire perdre tout un après-midi.

Un décalage d’horloge produit un autre symptôme : la signature est correcte, mais la requête est tout de même rejetée parce que son horodatage est trop ancien. Dans ce cas, vérifiez NTP, l’horloge des conteneurs et tout proxy inverse qui ajoute son propre horodatage. Un léger décalage de l’infrastructure peut faire penser à une signature invalide.

Si votre installation est également liée à WordPress ou à la facturation d’abonnements, l’article sur l’acceptation des paiements en USDT peut vous être utile. Il explique comment le traitement des webhooks, le contrôle des accès et la confirmation des paiements s’intègrent dans un même parcours.

Un dernier test peut être très révélateur : envoyez deux fois le même contenu. La première requête doit être acceptée. La seconde doit être rejetée si vous enregistrez les nonces et refusez leur réutilisation. Elle ne sera acceptée que si votre système de webhooks n’utilise pas de protection contre les rejeux basée sur les nonces. Ce doublon vous permet de vérifier que votre protection fonctionne vraiment.

Comments

Prêt à commencer ?

Créez un compte et émettez votre première facture en moins d’une heure.

Ce que cette page répond