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.
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
| Situation | Heute nutzen | Warum |
|---|---|---|
| In Sekunden auf Link- oder Domain-Änderungen reagieren | Webhook auf link.*, domain.* | Events werden gepusht; kein Klick-Event nötig |
| Live-Klickzähler in Ihrem Produkt | 1-Minuten-Abfrage von clicks/recent | Noch kein Klick-Webhook; 1.440 Requests am Tag liegen weit im Limit |
| CRM-Anreicherung pro Klick | Event-Forwarder oder kurze Abfrage | Eine mittlere Abfrageverzögerung von 30 Sekunden ist meist akzeptabel |
| Wöchentliches oder monatliches Reporting | summary und timeseries abfragen | Rückblickende Daten gewinnen durch Push nichts |
| Kein öffentlicher Endpunkt verfügbar | Polling | Webhooks brauchen eine https-URL, die aus dem Internet erreichbar ist |
| Ein Link-Event darf nicht verloren gehen | Webhook plus nächtlicher Abgleich | Retries 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.
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-Deliverymit 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
- Webhooks für Link-Events: Payloads, Signaturen, Retries
- Webhook-Signaturen prüfen: HMAC-SHA256 in Node, Python, Go
- API-Rate-Limits, Retries und Idempotenz beim URL-Shortener
- Link-Analytics-API: Klickstatistiken mit einem API-Key abrufen
- Fire-and-forget-Klickerfassung
- URL-Shortener-API und SDKs im Schnellstart
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.