9 Min. LesezeitIntegrationen

Webhooks vs. Polling: Click-Tracking-Webhooks im Vergleich

Webhooks vs. Polling beim Click-Tracking: Rechnung zu Latenz und Requests, Retries, Idempotenz, Signaturen.

Sasha Ehrlich
Compliance · EU residency

Nutzen Sie Webhooks, wenn ein Mensch oder ein Workflow binnen Sekunden reagieren muss, und fragen Sie per Polling ab, wenn ein paar Minuten Verzögerung in Ordnung sind und Sie lieber keinen öffentlichen Endpunkt betreiben. Bei Elido hat die Wahl einen Haken: Click-Tracking-Webhooks gibt es noch nicht. Webhooks decken Link-, Domain-, Mitglieds- und Workspace-Events ab, und ein Event click.created pro Klick ist geplant, wird aber nicht gesendet. Heute stammen Klickdaten aus dem Polling der Analytics-API.

Dieser Beitrag rechnet Verzögerung und Request-Volumen durch, behandelt, was jedes Muster garantiert (Retries, Duplikate, Reihenfolge, Signaturen), und endet mit einer Entscheidungstabelle. Der Code wurde vor der Aufnahme in Node 22 gegen lokale Mocks ausgeführt. Den vollständigen Katalog der Link-Events lesen Sie in Webhooks für Link-Events; für die Klick-Endpunkte siehe der Leitfaden zur Link-Analytics-API.

Webhooks vs. Polling in je einem Absatz

Polling bedeutet, dass Ihr Code nach einem Zeitplan nach neuen Daten fragt. Ein Cron-Job ruft GET /v1/workspaces/{workspace_id}/analytics/clicks/recent auf, verarbeitet, was neu ist, und schläft. Sie stoßen jeden Request an. Keine öffentliche URL nötig.

Webhooks kehren die Richtung um. Elido sendet einen signierten POST mit einem JSON-Envelope an Ihren HTTPS-Endpunkt, wenn ein Event ausgelöst wird, und eine 2xx-Antwort markiert die Zustellung als erledigt. Sie brauchen einen erreichbaren Endpunkt. TLS. Einen Handler, der Duplikate übersteht. Heute sind die Events link.created, link.updated, link.expired, domain.verified und der Rest der Liste in den Webhook-Dokumenten.

Diagramm im Vergleich: Beim Polling schickt Ihr Cron-Job alle fünf Minuten ein GET an die Elido-API und holt Zeilen; bei Webhooks schickt der Elido-Server einen POST an Ihren HTTPS-Endpunkt, der 2xx zurückgibt, wobei das Event click.created pro Klick weiter geplant ist. Die Seite 'Webhooks' in Elido mit einem Endpunkt samt abonnierten Link-Ereignissen, aktivem Status und Erstellungsdatum

Die Rechnung zu Latenz und Kosten

Die Polling-Verzögerung wird vom Intervall bestimmt. Bei einem Intervall von I Minuten wartet ein Event im Mittel I/2 Minuten und im schlimmsten Fall I Minuten, plus die Verzögerung, die die Daten ohnehin haben, bevor sie abfragbar sind. Requests pro Tag sind 1.440 / I.

  • 1 Minute: 1.440 Requests am Tag, 30 Sekunden mittlere Verzögerung, 1 Minute im schlimmsten Fall.
  • 5 Minuten: 288 Requests, 2,5 Minuten im Mittel, 5 im schlimmsten Fall.
  • 15 Minuten: 96 Requests, 7,5 Minuten im Mittel, 15 im schlimmsten Fall.
  • 60 Minuten: 24 Requests, 30 Minuten im Mittel, 60 im schlimmsten Fall.

Die Kosten zeigen sich als verschwendete Requests, nicht als Rate-Limit-Druck. Das veröffentlichte Workspace-Limit im Free-Tarif beträgt 10 Requests pro Sekunde, also 864.000 am Tag (siehe API-Rate-Limits und Idempotenz). Ein Poller im 1-Minuten-Intervall nutzt 1.440 / 864.000 = 0,17 % davon. An Limits stoßen Sie, wenn Sie pro Link über Hunderte von Links abfragen oder wenn sich andere Automatisierung den Workspace teilt, nicht mit einem Feed.

Die Verschwendung ist die Zahl, auf die es ankommt. Angenommen, ein Workspace ändert am Tag 40 Links und Sie beobachten das, indem Sie jede Minute abfragen. Höchstens 40 von 1.440 Requests liefern etwas Neues, also sind mindestens 1.400 (97 %) leer. Ein Webhook-Empfänger erhält für dieselben 40 Events 40 Requests. Im schlimmsten Fall, mit je drei Versuchen, sind es 120.

Webhooks gewinnen bei der Verzögerung, die typischerweise Sekunden beträgt, weil der Zustell-Worker ausstehende Zeilen laufend aufnimmt. Sie verlieren bei dem, was Polling gratis bekommt: Ein verpasster Lauf wird vom nächsten Lauf repariert, weil die Daten noch in der API liegen. Ein verpasster Webhook wird nur repariert, wenn Sie etwas zu seiner Reparatur gebaut haben.

Zustellgarantien: Was Elido verspricht

Dieses Verhalten stammt aus dem Zustellcode und den Hilfedokumenten, geprüft am 1. Oktober 2026.

Retries. Eine Zustellung erhält drei Versuche: einen sofort, einen 5 Minuten nach einem Fehlschlag, einen 15 Minuten danach, insgesamt etwa 20 Minuten. Jeder Status außer 2xx, jeder Netzwerkfehler und jede Antwort, die langsamer als 10 Sekunden ist, gilt als Fehlschlag. Nach dem dritten wird die Zustellung als fehlgeschlagen markiert; Retry im Dashboard oder POST .../deliveries/{delivery_id}/retry setzt sie mit einem frischen Budget von drei Versuchen wieder scharf. Zum Vergleich: Stripe wiederholt Live-Mode-Events bis zu drei Tage lang. Zwanzig Minuten sind kurz. Verlassen Sie sich nicht darauf, dass ein Ausfall des Empfängers aufgefangen wird.

Idempotenz. Die Zustellung ist at least once. Der Header X-Webhook-Delivery identifiziert eine Zustellung an einen Endpunkt und ändert sich weder bei automatischen noch bei manuellen Retries, er ist also Ihr Dedupe-Schlüssel.

Reihenfolge. Es gibt kein Reihenfolgeversprechen. Der Retry eines früheren Events kann nach einem späteren eintreffen, vergleichen Sie also data.updated_at mit dem, was Sie gespeichert haben, bevor Sie etwas überschreiben.

Signaturen. Jede signierte Zustellung trägt X-Webhook-Signature (Alias X-Elido-Signature) mit v1= plus dem Hex-HMAC-SHA256 von {timestamp}.{raw_body} und den Zeitstempel in X-Webhook-Timestamp. Der Zeitstempel ist bei jedem Versuch frisch, ein legitimer Retry ist also nie veraltet. Während einer Secret-Rotation trägt X-Elido-Signature-Previous sieben Tage lang eine Signatur des alten Secrets. Webhook-Signaturen prüfen behandelt Python, Go und das Debuggen einer Abweichung.

Transport und Pausieren. Neue Event-Endpunkte müssen https verwenden; einfaches http wird nur für localhost und 127.0.0.1 akzeptiert. is_active auf false zu setzen pausiert einen Endpunkt. Seit dem Fix vom 22. September stoppt das auch bereits eingereihte Zustellungen.

Ein Webhook-Empfänger in Node

Dieser Empfänger prüft die Signatur über die rohen Bytes, erzwingt ein Fünf-Minuten-Fenster, akzeptiert während der Rotation den Header mit dem vorherigen Secret und dedupliziert über die Delivery-ID. Die Toleranz von fünf Minuten ist Ihre Wahl, nicht die von Elido, und entspricht dem Stripe-Standard. Ich habe ihn gegen einen lokalen Signierer laufen lassen: Eine gültige Anfrage gab 200 zurück, ein Replay mit derselben Delivery-ID gab "duplicate" zurück, ein falsches Secret und ein 400 Sekunden alter Zeitstempel gaben beide 401 zurück.

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);

Die Längenprüfung vor timingSafeEqual ist wichtig. Node wirft bei Buffern unterschiedlicher Größe einen Fehler. Erst antworten, dann arbeiten. Bei der 10-Sekunden-Grenze wird ein langsamer, aber erfolgreicher Handler zum Duplikat.

Ein Klick-Poller in Node

Bis click.created erscheint, ist dies der Weg für Klicks. clicks/recent liefert das Neueste zuerst, akzeptiert limit bis 500 (Standard 100) und gibt next_cursor zurück, sobald eine Seite voll zurückkommt. Jeder Lauf liest von oben und hört auf, wenn er den neuesten bereits verarbeiteten Klick erreicht. Ich habe ihn gegen einen Mock laufen lassen, der zwei Zeilen pro Seite liefert: Der erste Lauf verarbeitete 5 Klicks, der zweite nur die 2 neuen, der dritte keinen.

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);

Zwei Grenzen, offen benannt. Der Cursor sortiert nach Zeitstempel und Link-ID, daher teilen sich zwei Klicks auf einen Link in derselben Sekunde einen Schlüssel; können Sie sich einen seltenen übersprungenen oder doppelten Klick nicht leisten, machen Sie handle über die ganze Zeile idempotent. Und lastSeen liegt im Arbeitsspeicher, persistieren Sie es also zwischen Neustarts, sonst spielt ein Neustart den jüngsten Verlauf erneut ab.

Noch nicht bereit, eines von beiden zu bauen? Das ist in Ordnung. Öffnen Sie das Dashboard und nutzen Sie zuerst die Dashboard-Analytics, dann fügen Sie die API hinzu, wenn Sie wissen, welche Zahlen Sie nach Zeitplan brauchen.

Entscheidungstabelle

SituationHeute nutzenWarum
In Sekunden auf Link- oder Domain-Änderungen reagierenWebhook auf link.*, domain.*Events werden gepusht; kein Klick-Event nötig
Live-Klickzähler in Ihrem Produkt1-Minuten-Abfrage von clicks/recentNoch kein Klick-Webhook; 1.440 Requests am Tag liegen weit im Limit
CRM-Anreicherung pro KlickEvent-Forwarder oder kurze AbfrageEine mittlere Abfrageverzögerung von 30 Sekunden ist meist akzeptabel
Wöchentliches oder monatliches Reportingsummary und timeseries abfragenRückblickende Daten gewinnen durch Push nichts
Kein öffentlicher Endpunkt verfügbarPollingWebhooks brauchen eine https-URL, die aus dem Internet erreichbar ist
Ein Link-Event darf nicht verloren gehenWebhook plus nächtlicher AbgleichRetries decken etwa 20 Minuten, die Abfrage deckt den Rest

Sobald der gesampelte Stream click.created erscheint, wandern die Zeilen für Live-Zähler und CRM in die Webhook-Spalte, und die Abfrage wird zum Abgleichsdurchlauf. Bis dahin sind Event-Forwarder wie Mixpanel-Link-Click-Events die Push-Option für Klickdaten.

Das Hybridmuster

Für alles, was Sie nicht verlieren dürfen, betreiben Sie beides. Der Webhook ist der schnelle Pfad. Eine geplante Abfrage vergleicht Summen und findet Lücken: Zustellungen, die ihre drei Versuche während eines Ausfalls aufgebraucht haben, oder Events, die in falscher Reihenfolge eintrafen.

Die Reports summary und timeseries liefern Summen und Tages-Buckets für einen Datumsbereich in einem Aufruf, was einen nächtlichen Vergleich billig macht. Bedenken Sie, dass der Bot-Filter Crawler und Monitore entfernt, bevor Klicks erfasst werden, Ihre Summen also nicht mit rohen Server-Logs übereinstimmen; der Analytics-Leitfaden erklärt, was ausgeschlossen wird. Den Rest deckt die Sicherheits-Checkliste ab. Wenn Sie eigene Webhooks entwerfen, ist die Standard-Webhooks-Spezifikation eine nützliche Referenz.

API-Dokumentationsseite im Elido-Dashboard mit Links zur API-Referenz, zu den SDKs, zur OpenAPI-Spezifikation und zur Verwaltung der API-Keys

Der API-Dokumentationsbildschirm eines DEMO-Workspaces mit Beispieldaten; es ist kein Kundenkonto.

Eine Checkliste vor dem Start für beide Muster

Gehen Sie sechs Prüfpunkte durch, bevor Sie einen Empfänger oder einen Poller ausliefern. Die meisten Produktionsvorfälle bei beiden Mustern entstehen, weil einer davon übersprungen wurde.

  • Webhook: Antworten Sie binnen 10 Sekunden mit 2xx und erledigen Sie die langsame Arbeit nach der Antwort.
  • Webhook: Prüfen Sie die Signatur über den rohen Body, bevor Sie JSON parsen.
  • Webhook: Speichern Sie X-Webhook-Delivery mit einem Unique-Index, damit ein Retry nicht doppelt zählt.
  • Poller: Persistieren Sie den Cursor oder den Last-seen-Schlüssel außerhalb des Prozessspeichers.
  • Poller: Gehen Sie bei einem 429 oder 5xx in den Backoff, statt im normalen Intervall zu wiederholen.
  • Beides: Alarmieren Sie, wenn länger als Ihre normale Ruhepause kein Event und kein neuer Klick eingetroffen ist, denn Stille ist die Art, wie eine kaputte Integration aussieht. Unabhängige Beiträge wie Hookdecks Leitfaden zu Webhook vs. API-Polling kommen zum selben Rat: Push für die Geschwindigkeit, Polling zum Abgleich.

Weitere Beiträge im Blog

Häufig gestellte Fragen

Was ist der Unterschied zwischen Webhooks und Polling?

Beim Polling fragt Ihr Code die API nach einem Zeitplan nach neuen Daten. Bei Webhooks schickt der Server einen HTTP-POST an Ihren Endpunkt, wenn etwas passiert. Polling kostet Requests und fügt bis zu ein Intervall an Verzögerung hinzu. Webhooks kosten einen dauerhaft erreichbaren öffentlichen Endpunkt und den Aufwand, Retries und Duplikate zu behandeln.

Hat Elido Click-Tracking-Webhooks?

Noch nicht. Webhooks decken Link-, Domain-, Mitglieds- und Workspace-Events wie link.created und link.expired ab. Ein Event click.created ist geplant, aber heute veröffentlicht nichts es. Für Daten auf Klickebene fragen Sie den Report clicks/recent der Analytics-API mit seinem Cursor ab oder nutzen einen Event-Forwarder.

Wie oft sollte ich eine Analytics-API abfragen?

Richten Sie das Intervall an der Entscheidung aus, die die Daten füttern. Eine Minute passt zu einem fast live aktualisierten Zähler, fünf bis fünfzehn Minuten zu Dashboards, stündlich oder täglich zu Reports. Eine Abfrage jede Minute ergibt 1.440 Requests am Tag, eine alle 5 Minuten 288, kürzer lohnt sich also selten.

Kommen Webhooks garantiert genau einmal an?

Nein. Die Webhook-Zustellung ist at least once, ein Empfänger sieht also nach einem Timeout oder einem manuellen erneuten Senden Duplikate. Elido behält über Retries hinweg denselben Wert von X-Webhook-Delivery bei, speichern Sie also diese ID und überspringen Sie alles, was Sie schon verarbeitet haben.

Was passiert, wenn mein Webhook-Endpunkt ausfällt?

Elido unternimmt drei Versuche: einen sofort, einen fünf Minuten nach einem Fehlschlag und einen fünfzehn Minuten danach. Schlagen alle drei fehl, wird die Zustellung als fehlgeschlagen markiert, bis jemand auf Retry drückt oder den Retry-Endpunkt aufruft. Ausfälle von mehr als etwa zwanzig Minuten brauchen einen Abgleich-Job.

Kann ich Webhooks und Polling kombinieren?

Ja, und bei allem, was zählt, sollten Sie das. Nutzen Sie den Webhook als schnellen Pfad und eine geplante Abfrage als Sicherheitsnetz, das Events findet, die Ihr Empfänger verpasst hat. Die Abfrage kann stündlich oder nachts laufen, weil sie nur Lücken schließen muss.

Elido testen

URL einfügen, kurzer Link in Sekunden

Kein Konto nötig. Link bleibt 30 Tage aktiv. Konto erstellen, um ihn dauerhaft zu behalten.

Keine Anmeldung nötig. Kostenlos registrieren für 50 Links im Monat.

Elido testen

URL-Shortener mit EU-Hosting: eigene Domains, tiefe Analytik und eine offene API. Kostenloser Tarif - keine Kreditkarte nötig.

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

Weiterlesen