vendredi 25 septembre 2026

À propos

Contact

Headless & API

Un connecteur Algolia dupliquait l’index à chaque webhook de publication

Chaque sauvegarde automatique d'un article déclenchait un webhook vers Algolia. Résultat : dix entrées identiques dans l'index pour un seul article publié.

Par Clément Hadrot • 2 janvier 2024 • 5 min de lecture • Aucun commentaire
Un connecteur Algolia dupliquait l'index à chaque webhook de publication

C’est un abonné du site qui a signalé le problème, via un message de contact presque amusé : « votre moteur de recherche me propose dix fois le même article sur la fiscalité des indépendants ». Le site en question, un média spécialisé en gestion d’entreprise, utilisait WordPress en back-office headless avec une recherche déléguée à Algolia, synchronisée via un webhook déclenché à chaque publication d’article.

En interrogeant l’index directement depuis le tableau de bord Algolia, le constat a été immédiat : l’article en question apparaissait bien dix fois, avec le même objectID, ce qui aurait dû être impossible puisqu’Algolia écrase normalement un objet existant portant le même identifiant plutôt que d’en créer un doublon. Le doublon n’était donc pas un doublon d’objet, mais un artefact d’un autre ordre.

Le symptôme observé plus en détail

En creusant dans les logs du webhook côté serveur intermédiaire (une petite fonction Node hébergée qui recevait le webhook WordPress et transformait la charge utile avant de l’envoyer à Algolia), il est apparu que le même article déclenchait bien dix appels à l’API d’indexation Algolia en l’espace de quelques minutes, tous avec un contenu strictement identique. Le mystère du tableau de bord affichant dix fois « le même » article venait en fait de dix tâches d’indexation asynchrones qui, à cause d’un léger décalage de timing, avaient chacune généré une entrée avec un objectID légèrement différent : l’identifiant était construit à partir de l’horodatage de la requête plutôt que de l’identifiant stable de l’article WordPress.

Le diagnostic : un webhook non débounced

WordPress déclenche l’action save_post à chaque enregistrement d’un article, y compris lors des sauvegardes automatiques du brouillon, programmées toutes les 60 secondes tant que l’éditeur reste ouvert sur l’écran d’édition. Le hook branché sur le webhook Algolia était accroché directement sur save_post, sans aucune vérification de l’état de publication ni du contenu réellement modifié :

L'essentiel à retenir : WordPress déclenche des sauvegardes automatiques toutes les 60 secondes par défaut sur un brouillon ouvert ; Sans debounce, chaque sauvegarde envoie un nouvel appel d'indexation vers Algolia ; Une file différée avec un identifiant de tâche unique évite les doublons
add_action('save_post', function ($post_id) {
    if (wp_is_post_revision($post_id)) {
        return;
    }
    wp_remote_post('https://mon-relais-webhook.exemple/publier', [
        'body' => wp_json_encode(['post_id' => $post_id]),
    ]);
});

Le garde-fou sur les révisions existait bien, mais il ne couvrait pas le cas d’une sauvegarde automatique de brouillon en cours d’édition (heartbeat), qui n’est pas une révision au sens WordPress du terme mais bien un appel à save_post à part entière. Un éditeur qui relisait son article en le laissant ouvert vingt minutes avant de cliquer sur « Publier » avait donc, sans le savoir, déclenché une vingtaine d’appels au webhook avant même la publication finale.

Pourquoi Algolia ne fusionnait pas les objets

Le relais Node générait l’objectID ainsi : `${post_id}-${Date.now()}`, un choix fait à l’origine pour garantir l’unicité de chaque objet en cas d’appels concurrents. Ce choix, sensé sur le papier, empêchait justement Algolia de reconnaître qu’il s’agissait toujours du même article et l’empêchait donc d’écraser l’entrée précédente comme il l’aurait fait avec un identifiant stable.

Le correctif : une file différée avec identifiant stable

Deux changements distincts ont réglé le problème. D’abord, l’objectID envoyé à Algolia est redevenu l’identifiant WordPress de l’article seul, sans horodatage, pour qu’un nouvel envoi remplace toujours proprement l’entrée existante plutôt que d’en créer une nouvelle :

const objectID = String(postId);

Ensuite, et surtout, l’appel au webhook a été mis en file avec un différé de trente secondes, annulé et redéclenché à chaque nouvel appel reçu pour le même article pendant ce délai :

const timers = new Map();

function planifierIndexation(postId, payload) {
  if (timers.has(postId)) {
    clearTimeout(timers.get(postId));
  }
  const t = setTimeout(() => {
    indexerDansAlgolia(payload);
    timers.delete(postId);
  }, 30000);
  timers.set(postId, t);
}

Avec ce debounce, une rafale de sauvegardes automatiques sur le même article ne déclenche plus qu’un seul appel réel à Algolia, trente secondes après la dernière modification détectée, au lieu d’un appel par sauvegarde intermédiaire.

Prévention pour les projets suivants

  • Ne jamais construire un identifiant d’objet Algolia à partir d’une donnée variable comme un horodatage quand l’entité source possède déjà un identifiant stable.
  • Différer systématiquement d’au moins quelques secondes tout webhook déclenché par save_post, pour absorber les sauvegardes automatiques rapprochées.
  • Vérifier le statut de publication (get_post_status) avant d’envoyer quoi que ce soit à un service tiers, pour ne synchroniser que les contenus réellement publiés.

Un webhook branché naïvement sur save_post est presque toujours un webhook qui va parler bien plus souvent que prévu ; le vrai travail consiste à décider quand ne pas l’écouter.

Ce qu’on retient

Ce genre de bug est trompeur parce que le symptôme visible, des doublons dans un index de recherche, oriente naturellement vers un problème côté Algolia, alors que la cause se situait entièrement côté WordPress et son relais webhook. La leçon générale dépasse Algolia : tout système de synchronisation déclenché par un hook WordPress doit anticiper la fréquence réelle des événements, heartbeat compris, et non seulement le cas nominal d’une publication unique et volontaire.

Partager :

À propos de l'auteur

Clément Hadrot

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

Voir tous ses articles

Dans la même veine

À lire aussi