Le WordPress d'aujourd'hui, décodé pour les développeurs

Headless & API

Un front Remix perdait le webhook Stripe pendant une bascule Cloudflare

Un paiement confirmé chez Stripe n'apparaissait jamais côté front Remix : une bascule réseau Cloudflare avait fait perdre le webhook, sans retry prévu.

Par WordPress Développement • 30 août 2023 • 4 min de lecture • Aucun commentaire
Un front Remix perdait le webhook Stripe pendant une bascule Cloudflare

Que se passe-t-il quand un client paie, reçoit son reçu Stripe par e-mail, mais que son compte reste marqué « en attente de paiement » sur le front ? C’est la question posée un vendredi après-midi par le support d’une boutique de formations en ligne, dont le frontend Remix consommait un WordPress headless comme source de vérité pour les commandes.

Le paiement était bel et bien confirmé côté Stripe. Le webhook censé notifier le backend WordPress de cette confirmation, en revanche, n’avait jamais atteint sa destination. L’enquête a mené vers une bascule réseau Cloudflare survenue exactement au moment du paiement, et vers l’absence totale de mécanisme de rattrapage côté serveur.

Symptôme : un paiement fantôme

Le tableau de bord Stripe affichait clairement l’événement checkout.session.completed comme émis avec succès. Côté WordPress, aucune trace de réception n’apparaissait dans les journaux de l’endpoint /wp-json/boutique/v1/webhook-stripe. Ni erreur, ni entrée : le silence complet, ce qui a d’abord orienté les recherches vers une mauvaise configuration de route plutôt que vers un problème réseau transitoire.

Diagnostic : une bascule de proxy en plein envoi

Les journaux Cloudflare ont révélé qu’une bascule automatique entre deux nœuds edge s’était produite quinze secondes avant l’horodatage de l’événement Stripe, dans le cadre d’une opération de maintenance planifiée côté Cloudflare. La requête POST du webhook, arrivée pile pendant cette fenêtre, a reçu une erreur 522 (timeout de connexion à l’origine) que Stripe a bien enregistrée comme un échec de livraison.

L'essentiel à retenir : Une bascule réseau de quelques secondes suffit à perdre un webhook si aucune file de retry ne le rattrape ; Stripe retente automatiquement un webhook en échec pendant plusieurs jours, mais seulement si l'endpoint répond une erreur claire ; Un accusé de réception rapide suivi d'un traitement asynchrone évite de bloquer Stripe sur un traitement métier lent

Le point critique : Stripe programme normalement des tentatives de renvoi automatiques pendant plusieurs jours en cas d’échec de livraison. Mais sur ce projet, l’endpoint retournait un code 200 même en cas d’erreur interne de traitement, pour des raisons historiques liées à un ancien script de débogage jamais retiré. Stripe considérait donc le webhook comme livré avec succès, alors que le traitement métier avait échoué silencieusement côté serveur au moment précis de la bascule réseau.

Correctif : distinguer réception et traitement

La première correction a consisté à séparer strictement l’accusé de réception du webhook de son traitement métier, en renvoyant systématiquement un code d’erreur HTTP explicite en cas d’échec réel :

add_action('rest_api_init', function () {
    register_rest_route('boutique/v1', '/webhook-stripe', [
        'methods' => 'POST',
        'callback' => 'traiter_webhook_stripe',
        'permission_callback' => '__return_true',
    ]);
});

function traiter_webhook_stripe(WP_REST_Request $request) {
    $payload = $request->get_body();
    $signature = $request->get_header('stripe-signature');

    try {
        $event = \Stripe\Webhook::constructEvent($payload, $signature, WEBHOOK_SECRET);
    } catch (\Exception $e) {
        return new WP_Error('signature_invalide', 'Signature Stripe invalide', ['status' => 400]);
    }

    // Mise en file d'attente plutôt que traitement synchrone
    wp_schedule_single_event(time(), 'traiter_evenement_stripe_async', [$event->id, $event->type, $event->data->object]);

    return new WP_REST_Response(['reçu' => true], 200);
}

Le traitement métier réel (mise à jour de commande, envoi de notification) a été déplacé vers un événement WP-Cron déclenché immédiatement après réception, avec sa propre gestion d’erreur et de nouvelle tentative en cas d’échec.

File de retry côté application

Pour couvrir le cas où même l’accusé de réception échouerait (comme lors de la bascule Cloudflare observée), une table dédiée enregistre chaque identifiant d’événement Stripe reçu, avec son statut de traitement. Une tâche cron planifiée toutes les cinq minutes interroge l’API Stripe pour lister les événements récents et compare avec cette table, rattrapant ainsi tout événement jamais reçu par l’endpoint :

  • Récupération des événements des dernières 24 heures via \Stripe\Event::all()
  • Comparaison avec la table locale des événements traités
  • Traitement différé de tout événement manquant, avec journalisation explicite

Prévention : simuler la panne avant qu’elle ne survienne

Stripe propose un mode de test permettant de renvoyer manuellement un événement passé depuis le tableau de bord, ce qui a servi de base à un scénario de test régulier : simuler un échec de livraison et vérifier que la file de rattrapage comble bien le vide dans un délai raisonnable, sans intervention manuelle.

Ce qu’il faut retenir

Un webhook n’est fiable que si son absence est détectable et rattrapable. Renvoyer un code 200 par réflexe, même en cas d’erreur de traitement interne, prive Stripe de son mécanisme de nouvelle tentative et transforme une panne réseau de quelques secondes en incident silencieux découvert des heures plus tard par un client insatisfait.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

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

Voir tous ses articles

Dans la même veine

À lire aussi