« La fiche produit numéro 891 est censée avoir été retirée il y a quatre jours, pourquoi apparaît-elle encore sur le site ? » Le signalement, remonté par l’équipe commerciale, décrit un décalage simple à formuler mais révélateur d’un défaut de conception fréquent sur les architectures headless qui reposent sur une génération incrémentale de pages statiques.
Vérification faite côté WordPress : le contenu a bien été supprimé, la requête vers l’API REST correspondante retourne désormais une erreur 404 conforme. Le problème ne se situe donc pas côté source de vérité, mais quelque part entre cette source et ce que le visiteur voit réellement affiché.
Le diagnostic : un webhook qui ne couvre qu’un seul événement
L’inspection du code qui déclenche la reconstruction des pages révèle la cause : le webhook de notification vers la plateforme d’hébergement du front n’est accroché qu’à l’action publish_post, un hook qui se déclenche à la publication ou à la mise à jour d’un contenu déjà publié, mais pas à sa suppression :
add_action( 'publish_post', function ( $post_id ) {
wp_remote_post( 'https://front.exemple.test/api/revalider', array(
'body' => array( 'chemin' => get_permalink( $post_id ) ),
) );
} );
Quand un contenu est supprimé, WordPress déclenche une action différente, before_delete_post ou deleted_post selon le moment précis souhaité dans le cycle de suppression, sans jamais passer par publish_post. Le webhook conçu uniquement autour de la publication ignore donc totalement l’événement de suppression, laissant la page statique déjà générée intacte, invisible à toute invalidation.
Pourquoi ce cas passe facilement inaperçu en test

Les scénarios de recette portent presque toujours sur la publication d’un nouveau contenu ou sa mise à jour, rarement sur sa suppression complète, jugée à tort comme un cas marginal. Ce biais de test explique pourquoi ce défaut peut survivre plusieurs mois en production avant qu’un contenu réellement supprimé ne révèle son existence.
Le correctif : couvrir l’ensemble des transitions de statut
La solution la plus robuste consiste à s’accrocher au hook générique transition_post_status, qui se déclenche pour tout changement de statut, y compris vers trash, plutôt qu’à des actions spécifiques à un seul type de transition :
add_action( 'transition_post_status', function ( $nouveau, $ancien, $post ) {
if ( $nouveau === $ancien ) {
return;
}
$chemin = $nouveau === 'publish'
? get_permalink( $post )
: home_url( '/produit/' . $post->post_name . '/' );
wp_remote_post( 'https://front.exemple.test/api/revalider', array(
'body' => array(
'chemin' => $chemin,
'action' => $nouveau === 'trash' ? 'supprimer' : 'mettre_a_jour',
),
) );
}, 10, 3 );
Côté front, la route /api/revalider doit interpréter le champ action pour distinguer une simple mise à jour, qui régénère la page, d’une suppression, qui doit au contraire retirer la page statique existante et faire répondre une erreur 404 pour toute requête ultérieure sur ce chemin.
Vérifier que le correctif fonctionne réellement
- Supprimer un contenu de test et vérifier que le webhook correspondant est bien déclenché, pas seulement lors d’une publication.
- Vérifier côté front que la page statique associée disparaît effectivement du cache après réception de l’événement.
- Ajouter un contrôle périodique qui compare un échantillon de contenus supprimés côté WordPress avec leur état réel côté front, en complément du mécanisme événementiel.
Un mécanisme d’invalidation pensé uniquement autour du cas le plus fréquent — la publication — laisse presque toujours filer le cas le plus rare mais le plus visible pour l’utilisateur final : la disparition d’un contenu qui reste, lui, obstinément affiché.
En résumé
Ce type d’incident rappelle qu’un système d’invalidation de cache doit être pensé autour de l’ensemble des transitions possibles d’un contenu, pas seulement de sa mise en avant la plus visible. Remplacer un hook spécifique par un hook générique couvrant toutes les transitions de statut referme durablement ce type de trou, à condition de vérifier explicitement le comportement attendu côté front pour chaque cas.