# « Invalid signature » sur un webhook Paddle mal vérifié côté WordPress

> Les webhooks Paddle se mettent soudain à être rejetés après un changement de format de charge utile. Diagnostic et correctif pour une vérification de signature qui a cessé de fonctionner.

- Auteur : Clément Hadrot
- Publié le : 2024-05-08
- Mis à jour le : 2024-05-08
- Catégorie : E-commerce
- URL : https://wpmoderne.dev.wordpress-developpement.fr/ecommerce/invalid-signature-webhook-paddle-mal-verifie-wordpress/

## L’essentiel

- Paddle est passé d'un format à signature classique à un format basé sur JSON signé
- Une vérification codée en dur sur l'ancien format rejette silencieusement tous les nouveaux webhooks
- Le correctif consiste à revalider le format de charge utile réellement reçu

« Invalid signature » — ce message apparaît dans les journaux d'erreur d'un site qui recevait pourtant sans problème les webhooks Paddle depuis des mois. Rien n'a changé côté code WordPress, et pourtant chaque notification de paiement, de renouvellement ou d'annulation d'abonnement se met à échouer à la vérification de signature, avec un code 401 renvoyé à Paddle qui retente alors l'envoi plusieurs fois avant d'abandonner.

## Symptôme : un rejet systématique après un changement invisible

Le point commun de la plupart des cas signalés : aucune modification du plugin d'intégration Paddle n'a été effectuée récemment. La cause se trouve du côté du fournisseur, pas du site : Paddle a fait évoluer le format de ses webhooks, passant d'un encodage classique en formulaire avec une signature HMAC calculée sur les paramètres triés, vers un format JSON avec une signature calculée différemment, incluant un horodatage dans l'en-tête `Paddle-Signature`. Un code de vérification écrit pour l'ancien format échoue systématiquement contre le nouveau, sans qu'aucune erreur explicite ne le signale autrement que ce rejet générique.

## Diagnostic : comparer le format reçu au format attendu

> L'essentiel à retenir : Paddle est passé d'un format à signature classique à un format basé sur JSON signé ; Une vérification codée en dur sur l'ancien format rejette silencieusement tous les nouveaux webhooks ; Le correctif consiste à revalider le format de charge utile réellement reçu

La première étape du diagnostic consiste à journaliser la charge utile brute reçue, avant toute tentative de vérification, pour observer sa structure réelle :

```
add_action( 'init', function() {
    if ( isset( $_SERVER['HTTP_PADDLE_SIGNATURE'] ) ) {
        error_log( 'En-tête Paddle-Signature : ' . $_SERVER['HTTP_PADDLE_SIGNATURE'] );
        error_log( 'Corps brut : ' . file_get_contents( 'php://input' ) );
    }
} );
```

Si le corps brut ressemble à un objet JSON commençant par `{"event_id":...` alors que le code de vérification tente de lire des paramètres `$_POST` classiques issus d'un formulaire, la cause est confirmée : le site tourne encore sur une logique de vérification pensée pour l'ancien système de notifications Paddle, incompatible avec le nouveau format basé sur les webhooks signés.

## Correctif : vérifier la signature selon le nouveau format

La vérification correcte du nouveau format consiste à extraire l'horodatage et la signature de l'en-tête, reconstruire la chaîne à signer en concaténant l'horodatage et le corps brut, puis comparer le résultat HMAC SHA-256 avec la clé secrète du webhook fournie dans le tableau de bord Paddle :

```
function wpm_verifier_signature_paddle( $corps_brut, $en_tete_signature, $cle_secrete ) {
    $parties = array();
    foreach ( explode( ';', $en_tete_signature ) as $segment ) {
        list( $cle, $valeur ) = explode( '=', $segment, 2 );
        $parties[ $cle ] = $valeur;
    }

    if ( empty( $parties['ts'] ) || empty( $parties['h1'] ) ) {
        return false;
    }

    $chaine_signee = $parties['ts'] . ':' . $corps_brut;
    $signature_calculee = hash_hmac( 'sha256', $chaine_signee, $cle_secrete );

    return hash_equals( $signature_calculee, $parties['h1'] );
}
```

Ce code remplace l'ancienne vérification qui reconstruisait la signature à partir de paramètres de formulaire triés alphabétiquement — une méthode devenue obsolète dès lors que Paddle envoie désormais un corps JSON brut et non plus des paires clé-valeur classiques.

### Rejouer les webhooks manqués

Une fois le correctif déployé, les webhooks rejetés pendant la panne ne sont pas perdus : le tableau de bord Paddle conserve un historique des notifications envoyées et permet de les rejouer manuellement, événement par événement, une fois la vérification de signature corrigée côté site. Il est recommandé de les rejouer dans l'ordre chronologique pour éviter qu'un événement d'annulation ne soit traité avant l'événement de création correspondant.

## Prévention pour la suite

- Journaliser systématiquement les webhooks rejetés, avec le motif exact du rejet, plutôt qu'un simple code 401 muet.
- Surveiller les annonces de changement d'API du fournisseur de paiement, généralement publiées plusieurs mois avant la bascule effective.
- Ajouter une alerte automatique déclenchée dès qu'un taux anormal de webhooks échoue sur une courte période.

> Ne renvoyez jamais un code 200 à un webhook dont la signature n'a pas pu être vérifiée : cela masquerait la panne au lieu de la signaler au fournisseur, qui cesserait alors de retenter l'envoi.

## Pour aller plus loin

Ce type de panne rappelle qu'une intégration de webhook ne doit jamais être considérée comme figée une fois écrite. Un fournisseur de paiement fait évoluer ses formats au fil du temps, et la seule protection durable consiste à surveiller activement les rejets de signature plutôt que d'attendre qu'un client signale une commande manquante en tableau de bord.
