11 min de lectureIntégrations

Webhooks ou polling : comparatif pour le suivi des clics

Webhooks ou polling pour le suivi des clics : calcul du délai et des requêtes, retries, idempotence et signatures, tableau de décision et code Node exécuté.

Sasha Ehrlich
Compliance · EU residency

Utilisez des webhooks quand une personne ou un flux de travail doit réagir en quelques secondes, et faites du polling quand quelques minutes de retard ne gênent pas et que vous préférez ne pas exposer de point de terminaison public. Chez Elido, le choix comporte un piège : les webhooks de suivi des clics n'existent pas encore. Les webhooks couvrent les événements de liens, de domaines, de membres et d'espaces de travail, et un événement click.created par clic est prévu mais pas émis. Aujourd'hui, les données de clics viennent de l'interrogation de l'API d'analytique.

Cet article fait le calcul du délai et du volume de requêtes, détaille ce que garantit chaque schéma (retries, doublons, ordre, signatures) et se termine par un tableau de décision. Le code a été exécuté sous Node 22 contre des simulations locales avant d'être publié. Pour le catalogue complet des événements de liens, lisez les webhooks pour les événements de liens ; pour les points de terminaison de clics, le guide de l'API d'analytique des liens.

Webhooks ou polling, un paragraphe chacun

Le polling signifie que votre code demande de nouvelles données selon un calendrier. Une tâche cron appelle GET /v1/workspaces/{workspace_id}/analytics/clicks/recent, traite ce qui est nouveau et se rendort. Vous initiez chaque requête. Aucune URL publique n'est nécessaire.

Les webhooks inversent le sens. Elido envoie un POST signé avec une enveloppe JSON à votre point de terminaison HTTPS quand un événement se déclenche, et une réponse 2xx marque la livraison comme terminée. Il vous faut un point de terminaison joignable. Du TLS. Un gestionnaire qui supporte les doublons. Aujourd'hui, les événements sont link.created, link.updated, link.expired, domain.verified et le reste de la liste dans la documentation des webhooks.

Schéma côte à côte : avec le polling, votre tâche cron envoie un GET à l'API Elido et récupère des lignes toutes les cinq minutes ; avec les webhooks, le serveur Elido pousse un POST vers votre point de terminaison HTTPS qui renvoie 2xx, l'événement click.created par clic restant prévu. La page 'Webhooks' d'Elido listant un point de terminaison avec ses événements de lien souscrits, un statut actif et la date de création

Le calcul du délai et du coût

Le retard du polling est fixé par l'intervalle. Avec un intervalle de I minutes, un événement attend I/2 minutes en moyenne et I minutes au pire, plus le délai que les données ont avant d'être interrogeables. Le nombre de requêtes par jour est 1 440 / I.

  • 1 minute : 1 440 requêtes par jour, 30 secondes de retard moyen, 1 minute au pire.
  • 5 minutes : 288 requêtes, 2,5 minutes en moyenne, 5 minutes au pire.
  • 15 minutes : 96 requêtes, 7,5 minutes en moyenne, 15 minutes au pire.
  • 60 minutes : 24 requêtes, 30 minutes en moyenne, 60 minutes au pire.

Le coût apparaît sous forme de requêtes gaspillées, pas de pression sur les limites de débit. La limite publiée par espace de travail sur Free est de 10 requêtes par seconde, soit 864 000 par jour (voir limites de débit de l'API et idempotence). Un seul poller à un intervalle d'une minute en utilise 1 440 / 864 000 = 0,17 %. Vous atteindrez les limites en interrogeant lien par lien sur des centaines de liens, ou quand d'autres automatisations partagent l'espace de travail, pas avec un seul flux.

Le gaspillage est le chiffre qui compte. Supposons qu'un espace de travail modifie 40 liens par jour et que vous le surveilliez en interrogeant chaque minute. Au plus 40 des 1 440 requêtes renvoient quelque chose de nouveau : au moins 1 400 (97 %) sont donc vides. Un récepteur de webhooks reçoit 40 requêtes pour les mêmes 40 événements. Au pire, avec trois tentatives chacun, cela fait 120.

Les webhooks gagnent sur le délai, qui se compte typiquement en secondes car le worker de livraison prend en charge les lignes en attente en continu. Ils perdent sur ce que le polling obtient gratuitement : une exécution manquée est réparée par la suivante, car les données sont toujours dans l'API. Un webhook manqué n'est réparé que si vous avez construit de quoi le réparer.

Garanties de livraison : ce qu'Elido promet

Ces comportements viennent du code de livraison et de la documentation d'aide, vérifiés le 1er octobre 2026.

Retries. Une livraison reçoit trois tentatives : une immédiate, une 5 minutes après un échec, une 15 minutes après la précédente, soit environ 20 minutes au total. Tout statut non 2xx, toute erreur réseau ou toute réponse plus lente que 10 secondes est un échec. Après la troisième, la livraison est marquée en échec ; Retry dans le tableau de bord ou POST .../deliveries/{delivery_id}/retry la réarme avec un nouveau budget de trois tentatives. À titre de comparaison, Stripe réessaie les événements en mode live jusqu'à trois jours. Vingt minutes, c'est court. Ne supposez pas qu'une panne du récepteur sera couverte.

Idempotence. La livraison est au moins une fois. L'en-tête X-Webhook-Delivery identifie une livraison vers un point de terminaison et ne change pas d'un retry automatique ou manuel à l'autre : c'est donc votre clé de dédoublonnage.

Ordre. Il n'y a aucune promesse d'ordre. Le retry d'un événement ancien peut arriver après un événement plus récent : comparez donc data.updated_at avec ce que vous avez stocké avant d'écraser quoi que ce soit.

Signatures. Chaque livraison signée porte X-Webhook-Signature (alias X-Elido-Signature) avec v1= suivi du HMAC-SHA256 hexadécimal de {timestamp}.{raw_body}, et l'horodatage dans X-Webhook-Timestamp. L'horodatage est renouvelé à chaque tentative, un retry légitime n'est donc jamais périmé. Pendant une rotation de secret, X-Elido-Signature-Previous porte pendant sept jours une signature issue de l'ancien secret. Vérifier les signatures de webhooks couvre Python, Go et le débogage d'une non-concordance.

Transport et mise en pause. Les nouveaux points de terminaison d'événements doivent utiliser https ; le http simple n'est accepté que pour localhost et 127.0.0.1. Mettre is_active à false met un point de terminaison en pause. Depuis le correctif du 22 septembre, cela arrête aussi les livraisons en file.

Un récepteur de webhooks en Node

Ce récepteur vérifie la signature sur les octets bruts, impose une fenêtre de cinq minutes, accepte l'en-tête du secret précédent pendant la rotation et dédoublonne sur l'identifiant de livraison. La tolérance de cinq minutes est votre choix, pas celui d'Elido, et elle correspond à la valeur par défaut de Stripe. Je l'ai exécuté contre un signataire local : une requête valide a renvoyé 200, un rejeu avec le même identifiant de livraison a renvoyé « duplicate », un mauvais secret et un horodatage vieux de 400 secondes ont tous deux renvoyé 401.

import http from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";

const SECRET = process.env.WEBHOOK_SECRET; // the whsec_... value
const seen = new Set(); // use a unique index in a real database

export function verify(secret, headers, rawBody, toleranceSec = 300) {
  const ts = headers["x-webhook-timestamp"] ?? "";
  if (!/^\d+$/.test(ts)) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSec) return false;
  const mac = createHmac("sha256", secret).update(`${ts}.`).update(rawBody);
  const expected = Buffer.from("v1=" + mac.digest("hex"));
  return ["x-elido-signature", "x-elido-signature-previous"].some((name) => {
    const got = Buffer.from(headers[name] ?? "");
    return got.length === expected.length && timingSafeEqual(got, expected);
  });
}

http
  .createServer((req, res) => {
    const chunks = [];
    req.on("data", (c) => chunks.push(c));
    req.on("end", () => {
      const raw = Buffer.concat(chunks);
      if (req.method !== "POST" || !verify(SECRET, req.headers, raw)) {
        return res.writeHead(401).end();
      }
      const id = req.headers["x-webhook-delivery"];
      if (seen.has(id)) return res.writeHead(200).end("duplicate");
      seen.add(id);
      const event = JSON.parse(raw.toString("utf8"));
      queueMicrotask(() => console.log("handled", event.type)); // do the work after replying
      res.writeHead(200).end("ok");
    });
  })
  .listen(8787);

La vérification de longueur avant timingSafeEqual compte. Node lève une exception sur des tampons de tailles différentes. Répondez d'abord, travaillez ensuite. Les 10 secondes sont la limite à partir de laquelle un gestionnaire lent mais réussi devient un doublon.

Un poller de clics en Node

Tant que click.created n'est pas disponible, c'est la voie des clics. clicks/recent renvoie les plus récents en premier, accepte limit jusqu'à 500 (100 par défaut) et renvoie next_cursor chaque fois qu'une page revient pleine. Chaque exécution lit depuis le haut et s'arrête quand elle atteint le clic le plus récent déjà traité. Je l'ai exécuté contre une simulation qui pagine deux lignes à la fois : la première exécution a traité 5 clics, la deuxième seulement les 2 nouveaux, la troisième aucun.

const BASE = "https://api.elido.app";
const WS = process.env.ELIDO_WORKSPACE_ID;
const KEY = process.env.ELIDO_API_KEY;

// Newest click already handled; clicks/recent is newest first.
let lastSeen = null;
const keyOf = (c) => `${c.ts}|${c.link_id}`;

export async function pollOnce(handle) {
  let cursor = null;
  let newest = null;
  for (;;) {
    const url = new URL(`${BASE}/v1/workspaces/${WS}/analytics/clicks/recent`);
    url.searchParams.set("limit", "500");
    if (cursor) url.searchParams.set("cursor", cursor);
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${KEY}` },
      signal: AbortSignal.timeout(10_000),
    });
    if (!res.ok) throw new Error(`clicks/recent returned ${res.status}`);
    const { items, next_cursor } = await res.json();
    for (const click of items) {
      if (keyOf(click) === lastSeen) {
        if (newest) lastSeen = newest;
        return;
      }
      newest ??= keyOf(click);
      await handle(click);
    }
    if (!next_cursor) break;
    cursor = next_cursor;
  }
  if (newest) lastSeen = newest;
}

// setInterval(() => pollOnce(handleClick).catch(console.error), 60_000);

Deux limites, énoncées franchement. Le curseur ordonne par horodatage et identifiant de lien : deux clics sur un même lien dans la même seconde partagent donc une clé ; si vous ne pouvez pas vous permettre un clic rarement sauté ou répété, rendez handle idempotent sur la ligne complète. Et lastSeen vit en mémoire : persistez-le entre les redémarrages, sinon un redémarrage rejoue l'historique récent.

Pas prêt à construire l'un ou l'autre ? Aucun souci. Ouvrez le tableau de bord et utilisez d'abord les statistiques du tableau de bord, puis ajoutez l'API quand vous saurez quels chiffres il vous faut de façon planifiée.

Tableau de décision

SituationÀ utiliser aujourd'huiPourquoi
Réagir en quelques secondes aux changements de liens ou de domainesWebhook sur link.*, domain.*Les événements sont poussés ; aucun événement de clic n'est nécessaire
Compteur de clics en direct dans votre produitInterrogation de clicks/recent chaque minutePas encore de webhook de clic ; 1 440 requêtes par jour restent largement dans la limite
Enrichissement CRM par clicRelais d'événements ou interrogation courteUn retard moyen de 30 secondes est généralement acceptable
Rapports hebdomadaires ou mensuelsInterroger summary et timeseriesLes données rétrospectives ne gagnent rien à être poussées
Aucun point de terminaison public disponiblePollingLes webhooks exigent une URL https joignable depuis Internet
Impossible de perdre un événement de lienWebhook plus rapprochement nocturneLes retries couvrent environ 20 minutes, l'interrogation couvre le reste

Quand le flux échantillonné click.created sera disponible, les lignes du compteur en direct et du CRM passeront dans la colonne des webhooks, et l'interrogation deviendra la passe de rapprochement. D'ici là, les relais d'événements comme les événements de clic de lien dans Mixpanel sont l'option de type push pour les données de clics.

Le schéma hybride

Pour tout ce que vous ne pouvez pas vous permettre de perdre, faites tourner les deux. Le webhook est la voie rapide. Une interrogation planifiée compare les totaux et trouve les trous : livraisons qui ont épuisé leurs trois tentatives pendant une panne, ou événements arrivés dans le désordre.

Les rapports summary et timeseries renvoient les totaux et les tranches quotidiennes pour une plage de dates en un seul appel, ce qui rend une comparaison nocturne peu coûteuse. N'oubliez pas que le filtre anti-bots supprime les robots d'indexation et les moniteurs avant l'enregistrement des clics : vos totaux ne correspondront donc pas aux journaux bruts du serveur ; le guide des statistiques explique ce qui est exclu. La liste de contrôle de sécurité couvre le reste. Si vous concevez vos propres webhooks, la spécification Standard Webhooks est une référence utile.

Page de documentation de l'API dans le tableau de bord Elido, avec des liens vers la référence de l'API, les SDK, la spécification OpenAPI et la gestion des clés d'API

L'écran de documentation de l'API d'un espace de travail DEMO avec des données d'exemple ; ce n'est pas un compte client.

Une liste de contrôle avant lancement pour l'un ou l'autre

Avant de livrer un récepteur ou un poller, passez six vérifications en revue. La plupart des incidents de production avec les deux schémas viennent de l'oubli de l'une d'elles.

  • Webhook : répondez en 2xx en moins de 10 secondes et faites le travail lent après la réponse.
  • Webhook : vérifiez la signature sur le corps brut avant d'analyser le JSON.
  • Webhook : stockez X-Webhook-Delivery avec un index unique pour qu'un retry ne puisse pas compter en double.
  • Poller : persistez le curseur ou la dernière clé vue hors de la mémoire du processus.
  • Poller : temporisez après un 429 ou un 5xx au lieu de réessayer à l'intervalle normal.
  • Les deux : alertez quand aucun événement ou nouveau clic n'est arrivé depuis plus longtemps que votre période de calme habituelle, car le silence est l'aspect que prend une intégration cassée. Des synthèses indépendantes comme le guide webhook vs API polling de Hookdeck aboutissent au même conseil : pousser pour la vitesse, interroger pour rapprocher.

À lire aussi sur le blog

Questions fréquentes

Quelle est la différence entre webhooks et polling ?

Avec le polling, votre code demande à l'API de nouvelles données selon un calendrier. Avec les webhooks, le serveur envoie une requête HTTP POST à votre point de terminaison quand quelque chose se produit. Le polling coûte des requêtes et ajoute jusqu'à un intervalle de retard. Les webhooks coûtent un point de terminaison public toujours disponible et le travail de gérer les retries et les doublons.

Elido propose-t-il des webhooks pour le suivi des clics ?

Pas encore. Les webhooks couvrent les événements de liens, de domaines, de membres et d'espaces de travail comme link.created et link.expired. Un événement click.created est prévu, mais rien ne le publie aujourd'hui. Pour les données au niveau du clic, interrogez le rapport clicks/recent de l'API d'analytique avec son curseur, ou utilisez un relais d'événements.

À quelle fréquence interroger une API d'analytique ?

Adaptez l'intervalle à la décision que les données alimentent. Une minute convient à un compteur quasi en direct, de cinq à quinze minutes à des tableaux de bord, et une heure ou un jour à des rapports. Une requête par minute fait 1 440 requêtes par jour et une toutes les 5 minutes en fait 288 : plus court vaut rarement la peine.

Les webhooks sont-ils garantis d'arriver exactement une fois ?

Non. La livraison des webhooks est au moins une fois : un récepteur voit donc des doublons après un délai d'attente dépassé ou un renvoi manuel. Elido conserve la même valeur X-Webhook-Delivery d'un retry à l'autre : stockez cet identifiant et ignorez tout ce que vous avez déjà traité.

Que se passe-t-il si mon point de terminaison de webhook est indisponible ?

Elido fait trois tentatives : une immédiate, une cinq minutes après un échec et une quinze minutes après la précédente. Si les trois échouent, la livraison est marquée en échec jusqu'à ce que quelqu'un appuie sur Retry ou appelle le point de terminaison de nouvel essai. Les pannes de plus d'une vingtaine de minutes demandent une tâche de rapprochement.

Puis-je combiner webhooks et polling ?

Oui, et pour tout ce qui compte vous devriez. Utilisez le webhook comme voie rapide et une interrogation planifiée comme filet de sécurité qui retrouve les événements que votre récepteur a manqués. L'interrogation peut tourner toutes les heures ou chaque nuit, puisqu'elle n'a qu'à combler les trous.

Essayer Elido

Collez une URL, obtenez un lien court

Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.

Sans inscription. Créez un compte gratuit pour 50 liens par mois.

Essayer Elido

Raccourcisseur d'URL hébergé en UE : domaines personnalisés, analyses approfondies et API ouverte. Forfait gratuit - sans carte bancaire.

Tags
webhooks vs polling
click tracking webhooks
click tracking api
url shortener webhooks
webhook retries idempotency
link analytics api

Lire la suite