Le message est arrivé un dimanche matin, transféré par la directrice des opérations d’une fondation européenne à but non lucratif, avec une seule ligne : merci de voir ci-dessous pour information et action. En dessous, un e-mail automatique de Stripe. Depuis plusieurs jours, Stripe n’arrivait plus à livrer les événements webhook au site de dons, dix-neuf tentatives avaient échoué et, sans changement, Stripe cesserait d’envoyer des notifications à cet endpoint dans moins d’une semaine.
Pour une association qui reçoit des dons ponctuels et mensuels via WordPress, ce n’est pas un détail. Les webhooks sont le moyen par lequel le site apprend qu’un paiement a réellement abouti. Sans eux, les dons restent en attente, les donateurs ne reçoivent pas leur reçu et les renouvellements mensuels ne sont pas enregistrés, alors même que l’argent continue d’arriver sur Stripe.
Voici comment nous avons remonté la piste, pourquoi le premier problème trouvé était réel sans être la cause, et le petit changement qui a tout réglé sans désactiver la moindre protection.
À quoi sert un webhook Stripe et pourquoi une panne compte
Quand un donateur paie, son navigateur part chez Stripe puis revient. Le site ne peut pas se fier à ce que dit le navigateur, donc Stripe appelle aussi le site directement, de serveur à serveur, avec un événement comme payment_intent.succeeded. Le site doit répondre avec un code HTTP entre 200 et 299. Tout le reste est un échec, et Stripe réessaie à intervalles croissants pendant trois jours au maximum avant d’abandonner l’événement.
Ici, le plugin de dons était GiveWP, qui écoute à une adresse se terminant par ?give-listener=stripe. Le site étant multilingue, il y avait deux endpoints actifs, l’un sous /de/, l’autre sous /fr/. Les deux échouaient, même si l’e-mail de Stripe n’en citait qu’un.

Première découverte : un vrai problème, mais pas la cause
Le tableau de bord WordPress affichait d’emblée une erreur d’activation. L’extension Recurring Donations avait été désactivée car elle exigeait une version de GiveWP plus récente que celle installée. L’extension avait été mise à jour, pas le plugin principal, et WordPress l’avait désactivée sans bruit. Sa licence n’était en outre pas renouvelée, elle ne recevait donc plus de mises à jour.
L’explication semblait parfaite, et il fallait de toute façon corriger cela. Sauvegarde complète, mise à jour de GiveWP, réactivation de l’extension. Le site public est aussitôt tombé sur une erreur fatale. La trace pointait vers une popup qui intégrait le shortcode d’un ancien formulaire supprimé. Nous avons désactivé l’extension pour rétablir le site, corrigé la popup, réactivé l’extension, puis vérifié l’accueil, la page de dons et chaque version linguistique.
Le site était de nouveau sain et l’option de don mensuel était revenue. Les webhooks, eux, échouaient toujours.
Lire la vraie erreur au lieu de deviner
Le moyen le plus rapide d’arrêter de deviner est d’ouvrir une livraison échouée dans le Dashboard Stripe, sous Développeurs, Webhooks, et de lire deux choses : le code de statut et le corps de la réponse. Ici, le statut était 403 Forbidden et le corps n’était pas du tout une erreur WordPress. C’était une page HTML intitulée Just a moment…, qui chargeait des scripts depuis challenges.cloudflare.com.

Cette page est le challenge navigateur de Cloudflare. Il demande au navigateur de prouver qu’il est humain avant de laisser passer la requête. Une personne sous Chrome le franchit en une seconde. Les serveurs de Stripe ne sont pas un navigateur, ne peuvent pas le résoudre, et chaque webhook était donc arrêté en bordure. La requête n’atteignait jamais WordPress, d’où l’absence totale de trace dans les journaux du site.
Identifier la fonction Cloudflare qui bloquait Stripe
Plusieurs fonctions de Cloudflare peuvent défier le trafic, et la correction dépend de celle qui est en cause. Dans Sécurité, Analyses, Événements, nous avons filtré sur la chaîne de requête give-listener. Chaque requête affichait l’action Managed Challenge et le service Security level.

La même liste révélait autre chose : des visiteurs ordinaires de Suisse, de France et d’Allemagne étaient eux aussi soumis au challenge sur des pages normales. Le niveau de sécurité de la zone avait été réglé très haut, sans doute après une attaque, et personne n’avait remarqué qu’il bloquait aussi le prestataire de paiement.
La solution : une règle Skip ciblée, pas un site affaibli
Baisser le niveau de sécurité aurait fonctionné, mais en modifiant la protection de tout le site pour un problème concernant une seule URL. La réponse propre est une règle personnalisée qui ne laisse passer que l’adresse du webhook et ne touche à rien d’autre.
- Allez dans Sécurité, Règles de sécurité et créez une règle personnalisée.
- Donnez-lui un nom clair, par exemple Allow Stripe webhooks.
- Réglez la condition : URI Query String, contains,
give-listener=stripe. - Choisissez l’action Skip et cochez All remaining custom rules.
- Ouvrez More components to skip et cochez Security Level et Browser Integrity Check.
- Placez la règle en premier et déployez-la.
(http.request.uri.query contains "give-listener=stripe")

Le détail facile à manquer est l’étape cinq. La première version de la règle n’ignorait que Browser Integrity Check, l’aperçu API affichait "products": ["bic"] et les webhooks auraient continué d’échouer. Le journal indiquait que le blocage venait de Security level : c’est donc ce composant qu’il faut ignorer. Pour l’offre gratuite : si le journal mentionne Bot Fight Mode, une règle Skip ne peut pas le contourner et il faut désactiver ce réglage.
Est-ce sûr ? La règle ne fait pas confiance aveuglément à la requête. GiveWP vérifie toujours chaque webhook avec la clé de signature Stripe, donc une requête falsifiée qui atteint le listener est rejetée par WordPress. Authentifier Stripe n’a jamais été le rôle de Cloudflare ; c’est celui de la signature.
Confirmer la correction et récupérer les dons manqués
Nous avons renvoyé un événement échoué depuis le Dashboard Stripe : réponse 200 OK. En moins d’une heure, les nouvelles tentatives automatiques ont réussi et le tableau de bord a marqué les échecs précédents comme Delivery recovered. Le lendemain, Stripe a confirmé par e-mail que l’endpoint était rétabli.

Deux remarques pratiques pour le nettoyage. D’abord, le bouton de renvoi ne fonctionne que sur la tentative la plus récente de chaque événement : pour ceux que Stripe a abandonnés, ouvrez l’événement lui-même et renvoyez-le depuis là. Ensuite, rapprochez l’argent, pas seulement les webhooks : comparez chaque paiement Stripe depuis le début de la panne avec les dons enregistrés dans WordPress. Ici, tous les paiements réussis étaient déjà enregistrés, un paiement avait réellement échoué pour fonds insuffisants et a été marqué comme tel, et deux petits paiements de test faits pendant l’enquête ont été identifiés pour ne pas être comptés comme des dons.
Leçons pour tout site WordPress qui encaisse des paiements
- Lisez d’abord le corps de la réponse. Un 403 qui contient du HTML de votre CDN n’est pas un bug applicatif, et aucun débogage de plugin ne le réglera.
- Sécurité et webhooks de paiement doivent s’accorder. Relever le niveau de sécurité après une attaque est raisonnable ; oublier qu’il s’applique aussi aux appels entre serveurs, c’est perdre des dons.
- Autorisez le chemin, pas le monde entier. Une règle Skip limitée au listener garde le reste du site protégé.
- Les licences expirées sont un risque lent. Elles coupent les mises à jour, et la prochaine mise à jour du cœur peut désactiver l’extension sans prévenir.
- Surveillez les alertes. Stripe prévient pendant environ une semaine avant de désactiver un endpoint. Assurez-vous que ces e-mails arrivent chez quelqu’un qui peut agir.
Nous avons décrit un cas voisin, où le cache et WordPress n’étaient pas d’accord sur le temps, dans une erreur 403 intermittente causée par la durée du cache, ainsi que les contrôles qui repèrent ces problèmes tôt dans ce que couvre vraiment la maintenance de site web.
Questions fréquentes
Pourquoi mes webhooks Stripe renvoient-ils 403 alors que le site fonctionne dans mon navigateur ?
Parce que votre navigateur réussit le challenge Cloudflare et pas les serveurs de Stripe. Si le corps de la réponse mentionne Just a moment ou challenges.cloudflare.com, la requête est stoppée chez Cloudflare avant d’atteindre WordPress.
Faut-il plutôt autoriser les adresses IP de Stripe ?
C’est possible avec la liste publiée par Stripe, mais elle évolue et doit être entretenue. Une règle Skip sur le chemin du webhook est plus simple et, comme le plugin vérifie la signature, tout aussi sûre en pratique.
Est-ce que je perds des dons pendant que les webhooks échouent ?
Stripe encaisse normalement l’argent, mais le site ne l’enregistre pas. Les dons restent en attente, les reçus ne partent pas et les renouvellements récurrents manquent jusqu’à la livraison des événements ou une correction manuelle.
Pendant combien de temps Stripe réessaie-t-il ?
En mode production, jusqu’à trois jours, à intervalles croissants. Si un endpoint continue d’échouer, Stripe envoie un avertissement par e-mail puis finit par le désactiver.
Vous gérez des dons, une boutique ou des adhésions sous WordPress et recevez des alertes de paiement que vous ne comprenez pas ? Nous menons exactement ce type d’enquête dans le cadre de nos services de développement WordPress et de maintenance. Parlons-en : nous vous dirons clairement ce qui ne va pas et ce qu’il faut pour le corriger.