Une commande existe dans WooCommerce, mais pas dans l’ERP. Une autre apparaît deux fois. Ces incidents peuvent venir du même problème : l’intégration confond l’émission d’un événement, sa réception et la réussite de l’opération métier. Il faut suivre ces trois étapes séparément.
Ce guide concerne les webhooks sortants de WooCommerce vers vos applications, pas les notifications entrantes d’un prestataire de paiement. Pour choisir l’architecture globale, consultez notre guide connecter WooCommerce à un CRM ou un ERP. Ici, l’objectif est de rendre un incident explicable et une reprise sûre.
Commande non transmise : établir une chronologie avant de relancer
Relevez l’identifiant de commande, la boutique source, son statut et les horaires avec leur fuseau. Distinguez une commande créée d’une commande éligible à l’export : le statut attendu dépend de vos paiements et de vos contrôles. Un événement order.created n’est pas une preuve de paiement.
- Dans WooCommerce → Réglages → Avancé → Webhooks, vérifiez le statut, le sujet et l’URL destinataire. Après des échecs répétés, un webhook peut être désactivé.
- Dans WooCommerce → État → Journaux, filtrez la source
webhooks-delivery. Recherchez la tentative, sa réponse HTTP et sa durée. Un journal vide n’établit pas à lui seul qu’aucun événement n’a existé. - Sur le destinataire, retrouvez la réception, la validation, l’enregistrement du travail puis son résultat. Un
200renvoyé par une page de connexion ou un proxy n’atteste pas une création dans l’ERP. - Si aucun envoi n’apparaît, contrôlez aussi les actions planifiées et leur retard. Si l’envoi existe mais n’atteint pas l’application, examinez DNS, TLS, pare-feu, redirection et route HTTP.
La documentation WooCommerce décrit ces écrans et la désactivation après échecs. Ne présumez pas qu’une réactivation rejouera toutes les commandes de la période : préparez un rattrapage explicite.

Classer l’erreur pour choisir la bonne action
Un refus d’authentification, une référence inconnue et une panne temporaire ne se corrigent pas de la même façon. Notez aussi quel système a produit le code : la réponse au webhook et celle de l’ERP appartiennent à deux échanges différents. Conservez un identifiant de corrélation entre ces étapes, avec un accès restreint aux journaux.
- 401 ou 403 : vérifier droits et configuration ; répéter sans correction ne rétablit pas l’accès.
- 429 : respecter les limites et l’indication
Retry-Afterlorsqu’elle est fournie. - Erreur de validation : corriger la donnée ou la correspondance avant reprise.
- Timeout ou 5xx : envisager une reprise espacée, mais traiter d’abord un éventuel résultat inconnu.
Vérifier la signature sur les octets reçus
Avec la configuration standard, WooCommerce signe le corps avec un HMAC-SHA256 encodé en Base64, transmis dans X-WC-Webhook-Signature. Calculez la signature attendue sur le corps brut, avant tout décodage puis réencodage JSON : des espaces ou un ordre de propriétés différents changent les octets signés. En PHP, comparez-la avec hash_equals(), en plaçant la valeur attendue en premier.
Choisissez le secret depuis une configuration serveur associée à une source connue, pas depuis une URL fournie par le message. Validez ensuite taille, JSON et schéma. La signature garantit l’authenticité du message pour ce secret ; elle ne bloque pas sa répétition. N’inscrivez ni secret ni données client complètes dans les URL ou les journaux.
Accuser réception après un enregistrement durable
Le destinataire devrait enregistrer durablement le travail validé avant de répondre rapidement en succès. Le traitement vers l’ERP peut ensuite se faire en arrière-plan. Répondre avant cet enregistrement crée une fenêtre de perte ; attendre une chaîne lente d’API augmente le risque de timeout. Si le stockage échoue, ne renvoyez pas un succès trompeur.
Dans WordPress, Action Scheduler peut exécuter des tâches différées. Ses fonctions doivent être appelées après son initialisation. Vérifiez le retour de planification, les tâches échouées et l’âge de la plus ancienne attente. Son option unique concerne la planification : elle ne rend pas, à elle seule, une création distante idempotente.
Si le registre du travail et sa planification sont deux écritures séparées, prévoyez aussi la panne entre les deux. Une opération persistée mais jamais planifiée ne doit pas rester invisible : un balayage des travaux en attente peut la remettre en file. Le redémarrage du serveur devient ainsi un scénario de recette, et non une hypothèse oubliée. Définissez enfin une durée de conservation adaptée pour les corps de messages et leurs données personnelles.
Éviter le doublon quand la réponse de l’ERP est perdue
Le cas difficile : l’ERP crée la commande, puis la connexion coupe avant que l’intégration reçoive la réponse. Un timeout signifie alors résultat inconnu, pas « rien n’a été créé ». Une nouvelle création aveugle peut doubler la vente.
Utilisez une clé stable par opération, par exemple boutique-a:commande:1842:creation. Le système cible doit la contrôler de manière atomique, avec une contrainte d’unicité ou un mécanisme d’idempotence documenté. Un test « existe déjà ? » suivi d’un ajout reste vulnérable si deux traitements passent ensemble. Conservez aussi la correspondance avec l’identifiant distant.
Si la cible ne garantit pas cette unicité, recherchez d’abord la référence externe après un résultat inconnu et prévoyez une résolution manuelle des ambiguïtés. Un verrou local seul ne couvre pas une panne après création distante. Les mises à jour et remboursements exigent des clés et des règles distinctes ; une seule clé par commande bloquerait ses évolutions légitimes.
Démonstration locale : deux réceptions, une seule création
L’exemple suivant fonctionne en PHP 8 avec PDO SQLite. Exécutez-le uniquement en ligne de commande dans un fichier de test hors du répertoire public. Il ne contacte aucun service, n’achète rien et utilise une base en mémoire. Le second passage simule un renvoi après perte de réponse. Ce laboratoire illustre la contrainte côté cible, pas un récepteur de production ni une file persistante.
<?php
$secret = random_bytes(32);
$body = '{"id":1842,"status":"processing"}';
$signature = base64_encode(hash_hmac('sha256', $body, $secret, true));
$db = new PDO('sqlite::memory:');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec('CREATE TABLE orders (external_ref TEXT PRIMARY KEY)');
$insert = $db->prepare(
'INSERT INTO orders (external_ref) VALUES (?)
ON CONFLICT(external_ref) DO NOTHING'
);
foreach ([1, 2] as $attempt) {
$expected = base64_encode(hash_hmac('sha256', $body, $secret, true));
if (!hash_equals($expected, $signature)) {
throw new RuntimeException('Invalid signature');
}
$insert->execute(['demo-store:order:1842:create']);
}
echo 'orders=' . $db->query('SELECT COUNT(*) FROM orders')->fetchColumn() . PHP_EOL;
$changed = str_replace('1842', '1843', $body);
$expected = base64_encode(hash_hmac('sha256', $changed, $secret, true));
echo 'tampered=' . (hash_equals($expected, $signature) ? 'accepted' : 'rejected') . PHP_EOL;
Résultat attendu : orders=1, puis tampered=rejected. Cela vérifie un doublon séquentiel et un corps modifié. Il reste à tester concurrence, redémarrage et comportement réel de l’API cible en préproduction ; cette simulation ne prouve pas ces propriétés.
Les tests d’acceptation à rendre déterministes
- Deux traitements simultanés : une seule création métier, sans neutraliser une autre commande.
- Coupure après création distante : la reprise retrouve cette création, sans en ajouter une.
- ERP indisponible puis rétabli : le travail reste conservé et reprend selon une politique bornée et observable.
- Signature invalide : aucun travail accepté ni donnée commerciale créée.
- Notification ancienne après une mise à jour : aucun retour silencieux à un statut antérieur ; vérifier l’état actuel ou appliquer une règle de version.
- Référence produit inconnue : erreur explicite, sans remplacement improvisé.
Rapprocher les commandes pour retrouver les événements manqués
Comparez périodiquement les commandes éligibles côté boutique avec les références enregistrées à destination. Parcourez les résultats paginés et utilisez une fenêtre qui se chevauche pour tolérer retard et décalage d’horloge. Le rattrapage réutilise les mêmes clés d’opération que le flux normal. Commencez par un rapport des écarts, puis validez le périmètre avant tout envoi.
Surveillez enfin le retard, les opérations bloquées, les résultats inconnus et la date du dernier rapprochement réussi. Documentez qui peut rejouer quoi. Pour une intégration WooCommerce sur mesure, cette procédure de reprise compte autant que le premier envoi réussi. Notre cas e-commerce et contenus privés présente un périmètre réel mêlant boutique, CRM interne et droits d’accès. Présentez-nous le flux et l’incident rencontré pour cadrer le diagnostic.
Références techniques : webhooks WooCommerce, implémentation WC_Webhook, API Action Scheduler et HMAC en PHP. Vérifiées le 20 octobre 2026 ; adaptez les contrôles à vos versions et extensions.
