# Deux webhooks arrivaient dans le désordre sur un front headless

> Un email de confirmation parti avant que le paiement ne soit validé côté serveur : diagnostic d'un ordre d'arrivée non garanti entre deux webhooks, correctif par file ordonnée.

- Auteur : Clément Hadrot
- Publié le : 2025-01-23
- Mis à jour le : 2025-01-23
- Catégorie : Headless &amp; API
- URL : https://wpmoderne.dev.wordpress-developpement.fr/headless/deux-webhooks-desordre-front-headless/

## L’essentiel

- Ordre d'arrivée des webhooks jamais garanti par le fournisseur de paiement
- État incohérent entre confirmation email et validation réelle
- File ordonnée par identifiant de commande comme correctif

« Merci pour votre commande, votre paiement est confirmé » : l'email était parti, mais la commande, elle, restait affichée en statut « en attente de paiement » sur le compte client. L'incident, remonté par un client agacé d'avoir reçu une confirmation contredite par son propre espace personnel, concernait un site headless combinant WooCommerce comme moteur de commande et un prestataire de paiement tiers notifiant sa confirmation par webhook.

## Symptôme : deux messages contradictoires pour une même commande

Le prestataire de paiement envoyait deux webhooks distincts pour chaque transaction réussie : un premier, générique, signalant que la transaction avait été autorisée par la banque, et un second, quelques dizaines de millisecondes plus tard, confirmant que les fonds avaient bien été capturés. Le front avait été conçu pour déclencher l'envoi de l'email de confirmation dès réception du premier webhook, et la mise à jour du statut de commande WooCommerce à réception du second. Le problème : rien ne garantissait que ces deux webhooks arriveraient dans cet ordre.

## Diagnostic : l'ordre d'arrivée réseau n'est jamais garanti

L'investigation, menée à partir des journaux d'accès horodatés au niveau serveur, a révélé que dans environ 6 % des transactions, le second webhook (capture des fonds) arrivait avant le premier (autorisation), avec un écart moyen de 40 millisecondes entre les deux. Cette inversion, invisible dans la documentation du prestataire de paiement qui ne garantissait explicitement aucun ordre de livraison, suffisait à créer l'incohérence observée : l'email partait sur la base du premier webhook reçu, quel qu'il soit, sans vérifier que la commande avait réellement atteint l'état final attendu.

> L'essentiel à retenir : Ordre d'arrivée des webhooks jamais garanti par le fournisseur de paiement ; État incohérent entre confirmation email et validation réelle ; File ordonnée par identifiant de commande comme correctif

## Pourquoi ce problème est spécifique aux architectures headless

Sur un WordPress classique avec un thème monolithique, ce type de webhook transite généralement par une seule route, traitée séquentiellement par le même processus PHP, ce qui masque souvent ce genre de désordre. En architecture headless, deux webhooks distincts peuvent arriver sur deux instances serverless différentes, exécutées en parallèle sans aucune garantie d'ordre, ce qui rend le problème bien plus probable et bien plus difficile à reproduire en environnement de test local.

## Le correctif : une file ordonnée par identifiant de commande

La solution retenue introduit une file d'attente (une simple table dédiée, avec un verrou logique par identifiant de commande) qui traite les webhooks entrants dans un ordre logique déterminé par leur contenu, et non par leur ordre d'arrivée réseau :

```
function handle_payment_webhook( $payload ) {
    $order_id = $payload['order_id'];
    $event    = $payload['event']; // 'authorized' ou 'captured'

    $lock = wp_cache_add( "webhook_lock_{$order_id}", 1, '', 5 );
    if ( ! $lock ) {
        // un autre webhook pour cette commande est en cours de traitement
        wp_schedule_single_event( time() + 2, 'retry_payment_webhook', array( $payload ) );
        return;
    }

    if ( $event === 'captured' && get_post_meta( $order_id, '_payment_authorized', true ) !== '1' ) {
        // la capture arrive avant l'autorisation : on la met en attente
        update_post_meta( $order_id, '_pending_capture', $payload );
        wp_cache_delete( "webhook_lock_{$order_id}" );
        return;
    }

    process_payment_event( $order_id, $event, $payload );
    wp_cache_delete( "webhook_lock_{$order_id}" );
}
```

Ce mécanisme garantit qu'un webhook de capture arrivant en avance est mis en attente jusqu'à réception effective du webhook d'autorisation correspondant, plutôt que d'être traité isolément. L'email de confirmation n'est désormais envoyé qu'après traitement complet et cohérent des deux événements, quel que soit leur ordre d'arrivée réel.

## Prévention : documenter l'absence de garantie d'ordre

- Ne jamais supposer qu'un fournisseur externe garantit l'ordre de livraison de ses webhooks, sauf mention explicite contraire dans sa documentation.
- Traiter chaque webhook comme un événement isolé, à recombiner logiquement plutôt qu'à interpréter séquentiellement.
- Ajouter un test automatisé simulant volontairement l'inversion des deux webhooks avant chaque mise en production d'une nouvelle intégration de paiement.

> Un webhook n'est jamais une garantie d'ordre, seulement une garantie (parfois relative) de livraison. Construire une logique métier qui suppose l'un des deux sans l'avoir vérifié explicitement dans la documentation du fournisseur revient à parier sur un comportement non contractuel.

## Notre verdict

Ce type d'incident, difficile à reproduire en local tant il dépend de conditions réseau réelles, rappelle qu'une architecture événementielle distribuée doit être pensée pour l'inversion, pas pour l'ordre idéal. La file ordonnée par identifiant de commande, bien que légèrement plus complexe à mettre en œuvre qu'un simple traitement séquentiel, reste la seule garantie robuste face à des webhooks dont l'ordre d'arrivée échappe totalement au contrôle du site qui les reçoit.
