Zwei Teams, die auf derselben URL-Shortener-API aufbauen, enden oft mit völlig unterschiedlichen Integrationsarchitekturen. Ein Team richtet einen Webhook-Endpoint ein und reagiert in Echtzeit auf jeden Klick. Das andere schreibt einen Cron-Job, der alle fünf Minuten die Analytics-API abfragt. Beides ist gültig. Die Entscheidung zwischen beiden hat reale Konsequenzen für Latenz, operativen Overhead und wie stark Ihr System degradiert, wenn etwas schiefgeht.
Dieser Beitrag legt die tatsächlichen Trade-offs dar.
Die beiden Muster#
Polling#
Polling bedeutet, dass Ihr Code die API nach einem Zeitplan nach aktuellen Klickdaten fragt. Ein Cron-Job wacht auf, ruft /v1/analytics/workspaces/{id}/clicks/recent oder /v1/analytics/workspaces/{id}/summary auf, verarbeitet die Ergebnisse und schläft dann bis zum nächsten Intervall.
Der Datenfluss ist pull-basiert: Ihre Infrastruktur initiiert jede Interaktion. Der API-Server weiß nichts über Ihre internen Systeme - er beantwortet nur die Abfragen, die Sie senden.
Webhooks#
Webhooks bedeuten, dass Elidos Server kurz nach der Verarbeitung eines Klicks ein click.recorded-Ereignis an Ihren HTTPS-Endpoint pusht. Ihr Empfänger verarbeitet es, gibt eine 2xx zurück, und die Zustellung wird als erfolgreich verzeichnet.
Der Datenfluss ist push-basiert: Die Plattform initiiert den Kontakt. Ihr Endpoint muss vom Internet aus erreichbar sein, benötigt TLS und muss zuverlässig antworten.
Wann Polling die richtige Wahl ist#
Polling passt zu einem bestimmten Set von Bedingungen. Wenn die meisten davon auf Ihre Situation zutreffen, beginnen Sie mit Polling und greifen Sie nur dann zu Webhooks, wenn ein konkretes Problem Sie dazu zwingt.
Sie kontrollieren beide Seiten der Integration. Wenn der Konsument ein Dashboard oder Reporting-Tool ist, das Sie besitzen und betreiben, liefert Polling vorhersehbares, beschränktes Verhalten. Sie entscheiden über das Intervall; Sie entscheiden über das Zeitfenster; Sie entscheiden, wie mit Teilergebnissen umgegangen wird.
Ihr Anwendungsfall ist retrospektiv. Wöchentliche Kampagnenberichte, monatliche Aggregations-Jobs und Abgleichs-Pipelines profitieren nicht von Sub-Minuten-Latenz. Ein Cron-Job, der stündlich gegen /summary oder /breakdown/country läuft, ist architektonisch einfacher und leichter zu verstehen als ein zustandsbehafteter Webhook-Empfänger mit Retry-Behandlung.
Sie haben keinen öffentlichen Endpoint zum Bereitstellen. Webhooks erfordern eine URL, die von Elidos Infrastruktur aus erreichbar ist. Wenn Ihre Integration innerhalb eines privaten Netzwerks läuft, einer Lambda-Funktion ohne stabile URL oder der lokalen Maschine eines Entwicklers, kann die Einrichtung eines eingehenden HTTPS-Endpoints mehr operative Komplexität kosten, als der Latenzvorteil wert ist.
Das Volumen ist gering. Bei einigen tausend Klicks pro Tag ist der Unterschied zwischen Echtzeit und fünf Minuten Verzögerung für Endnutzer selten sichtbar. Polling ist einfach zu verstehen, einfach zu debuggen und produziert keine Infrastrukturüberraschungen.
Wann Webhooks die richtige Wahl sind#
Webhooks sind sinnvoll, wenn Latenz eine Produktanforderung statt einer netten Beigabe ist.
Sie bauen einen Live-Zähler oder eine Echtzeit-UX. Wenn Ihr Produkt Nutzern einen Klickzähler zeigt, der sichtbar innerhalb von Sekunden nach einer Weiterleitung aktualisiert wird, wird Polling in einem vernünftigen Intervall spürbar veraltet wirken. Ein Webhook-Handler, der einen Redis-Zähler bei click.recorded-Ereignissen inkrementiert und ihn über eine WebSocket- oder SSE-Verbindung zum Frontend hervorhebt, ist die Architektur, die dies erreicht, ohne die Analytics-API zu hämmern.
Sie reichern CRM-Datensätze pro Klick an. Ein Klick-Ereignis an einen Kontaktdatensatz zu binden - zu identifizieren, welcher spezifische Prospect dem Link in Ihrer Outbound-E-Mail gefolgt ist und seine CRM-Timeline zu aktualisieren - ist zeitkritisch. Bis ein Polling-Job fünf Minuten später aufholt, hat der Vertriebsmitarbeiter möglicherweise schon angerufen. Ein Webhook-Handler, der innerhalb von Sekunden nach dem Klick ein CRM-Update auslöst, ist das richtige Werkzeug.
Sie betreiben ereignisgesteuerte Workflows. Workflows, die durch Klickereignisse ausgelöst werden - eine Follow-up-E-Mail senden, wenn ein Link geklickt wird, das Segment eines Abonnenten aktualisieren, einen Inventarwert dekrementieren - sind natürliche Webhook-Konsumenten. Das click.recorded-Ereignis trägt genug Daten, um sofort zu handeln, ohne eine Round-Trip-Abfrage.
Sie haben einen stabilen, öffentlich erreichbaren HTTPS-Endpoint. Das ist die Voraussetzung, von der alles andere abhängt. Wenn Sie bereits über Produktionsinfrastruktur verfügen, die eingehende Webhooks von anderen Anbietern (Stripe, GitHub, Twilio) akzeptiert, ist das Hinzufügen von Elido zum gleichen Empfänger reibungsarm.
Die versteckten Kosten von Webhooks#
Webhooks klingen einfach: Server sendet POST, Sie verarbeiten es. Die reale Implementierungsoberfläche ist größer.
Signaturverifizierung#
Elido signiert jede Webhook-Zustellung mit HMAC-SHA256. Das Signaturformat ist v1=HMAC-SHA256(secret, "${unix_timestamp}.${body}"), geliefert im X-Webhook-Signature-Header. Der Zeitstempel wird separat in X-Webhook-Timestamp gesendet. Beides wird in services/webhook-dispatcher/internal/signing/hmac.go produziert.
Sie müssen diese Signatur verifizieren, bevor Sie die Nutzlast verarbeiten. Ein Empfänger, der die Verifizierung überspringt, verarbeitet jeden POST, der den Endpoint erreicht, einschließlich gefälschter Anfragen von jedem, der Ihre Webhook-URL entdeckt.
Hier ist ein minimaler Express-Handler in TypeScript, der die Signatur verifiziert, bevor er irgendetwas mit der Nutzlast tut:
import express, { Request, Response } from "express";
import crypto from "crypto";
const app = express();
// Use raw body middleware - JSON parsers consume the stream before you can hash it
app.use("/webhook", express.raw({ type: "application/json" }));
function verifySignature(
secret: string,
signature: string,
timestamp: string,
rawBody: Buffer,
): boolean {
const message = `${timestamp}.${rawBody.toString("utf8")}`;
const expected =
"v1=" + crypto.createHmac("sha256", secret).update(message).digest("hex");
// Use timingSafeEqual to prevent timing-based enumeration
return crypto.timingSafeEqual(
Buffer.from(signature, "utf8"),
Buffer.from(expected, "utf8"),
);
}
app.post("/webhook", (req: Request, res: Response) => {
const signature = req.headers["x-webhook-signature"] as string;
const timestamp = req.headers["x-webhook-timestamp"] as string;
if (!signature || !timestamp) {
return res.status(400).json({ error: "missing signature headers" });
}
// Reject payloads older than 5 minutes
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
if (age > 300) {
return res.status(400).json({ error: "payload too old" });
}
if (
!verifySignature(
process.env.WEBHOOK_SECRET!,
signature,
timestamp,
req.body as Buffer,
)
) {
return res.status(401).json({ error: "invalid signature" });
}
const event = JSON.parse((req.body as Buffer).toString("utf8"));
if (event.type === "click.recorded") {
// Handle the click event
console.log("click recorded:", event.data);
}
// Always return 2xx promptly - do heavy processing async
return res.status(200).json({ received: true });
});
Das Replay-Fenster#
Die Zeitstempelprüfung im obigen Beispiel setzt durch, was Elidos Dokumentation ein Replay-Fenster nennt. Ohne sie kann ein Angreifer, der eine einzige gültige signierte Nutzlast erfasst, sie unbegrenzt wiederholen - die Signatur bleibt für immer gültig, weil sie aus einem festen Zeitstempel berechnet wird. Mit der Prüfung wird eine Nutzlast, die älter als fünf Minuten ist, abgelehnt, unabhängig davon, ob die Signatur gültig ist.
Setzen Sie die Toleranz auf etwas, das Ihre Infrastruktur bewältigen kann. Fünf Minuten ist der konventionelle Standard und entspricht dem, was Stripe verwendet. Wenn Ihr Empfänger während Deployments gelegentlich für einige Minuten offline ist, gibt dieses Fenster ihm Zeit, wieder hochzufahren und dennoch in Flight befindliche Zustellungen zu verarbeiten.
Wiederholungen und Idempotenz#
Elidos webhook-dispatcher wiederholt fehlgeschlagene Zustellungen nach einem Backoff-Zeitplan: erster Retry nach 1 Minute, zweiter nach 5 Minuten, dritter nach 15 Minuten. Der Wert für maximale Versuche pro Zustellung beträgt standardmäßig 3, wie im webhook_deliveries-Schema definiert. Nach 3 fehlgeschlagenen Versuchen wird die Zustellung dauerhaft als fehlgeschlagen markiert und im Benachrichtigungs-Dashboard angezeigt.
Das bedeutet, Ihr Empfänger kann dasselbe Ereignis mehr als einmal empfangen. Jede Verarbeitung mit Seiteneffekten - Schreiben in eine Datenbank, Senden einer E-Mail, Aktualisieren eines Zählers - muss idempotent sein. Der X-Webhook-Delivery-Header trägt eine stabile Zustellungs-ID, die Sie als Idempotenzschlüssel verwenden können.
// Before processing, check whether this delivery has already been handled
const deliveryId = req.headers["x-webhook-delivery"] as string;
const alreadyProcessed = await redis.get(`webhook:delivery:${deliveryId}`);
if (alreadyProcessed) {
return res.status(200).json({ received: true, duplicate: true });
}
// Mark as processed with a TTL that covers the retry window
await redis.set(`webhook:delivery:${deliveryId}`, "1", "EX", 3600);
Ihr Endpoint muss hochverfügbar sein#
Das Retry-Fenster ist endlich. Wenn Ihr Empfänger länger als etwa 21 Minuten (1 + 5 + 15) ausgefallen ist, erschöpfen Zustellungen ihre Versuche und schlagen dauerhaft fehl. Für Ereignisse, bei denen garantierte Zustellung wichtig ist - CRM-Anreicherung, Abrechnungs-Hooks - benötigt Ihre Empfänger-Infrastruktur ordentliche Verfügbarkeit, nicht einen Hobby-Server, der gelegentlich neu startet.
Das sind die am stärksten unterschätzten Kosten von Webhooks für Teams, die neu im eingehenden HTTP sind. Polling degradiert anmutig: Wenn der Polling-Job fehlschlägt, läuft er einfach im nächsten Intervall erneut und holt auf. Ein Webhook-Empfänger, der nicht verfügbar ist, verliert Ereignisse dauerhaft, es sei denn, Sie haben eine Abgleichsstrategie.
Die versteckten Kosten von Polling#
Polling sieht von außen einfach aus. Die realen Kosten häufen sich in der Produktion an.
Verzögerung ist die definierende Einschränkung. Ein Cron-Job, der alle fünf Minuten läuft, bedeutet, dass Klickdaten bis zu fünf Minuten veraltet sind. Für die meisten retrospektiven Anwendungsfälle ist das akzeptabel; für alles Nutzerseitige ist es das nicht. Das Intervall zu verkürzen hilft, eliminiert aber die Verzögerung nicht, und sehr kurze Intervalle (unter einer Minute) sehen eher nach API-Hämmern aus als nach Polling.
Verschwendete Anfragen. Die meisten Polling-Intervalle geben dieselben Daten wie die vorherige Anfrage zurück. Wenn Sie einen verkehrsarmen Link jede Minute pollen und Klicks ungefähr einmal pro Stunde eintreffen, geben 59 von 60 Anfragen nichts Neues zurück. Diese Anfragen zählen weiterhin gegen Ihr API-Ratenlimit.
Ratenlimits. Elidos API setzt Per-Workspace-Ratenlimits durch, dimensioniert nach Abrechnungstier. Ein Polling-Job, der häufig über viele Links in einem großen Workspace läuft, kann diese Limits erreichen, besonders wenn andere Automatisierung im selben Workspace ebenfalls API-Aufrufe macht. Die API gibt 429 Too Many Requests mit einem X-RateLimit-Scope: workspace-Header zurück, wenn dies passiert.
Paginierung und verpasste Ereignisse. Der /clicks/recent-Endpoint verwendet Cursor-basierte Paginierung. Wenn Sie auf ein festes Zeitfenster pollen - ?from=<last_poll>&to=<now> - und das Volumen in diesem Fenster die Seitengröße überschreitet, verpassen Sie Ereignisse, es sei denn, Sie folgen next_cursor durch alle Seiten. Eine Polling-Implementierung, die Paginierung nicht behandelt, verliert unter Last stillschweigend Daten.
Das hybride Muster#
Für die meisten Produktionsanwendungsfälle ist die beste Antwort nicht entweder/oder.
Verwenden Sie Webhooks als primären Pfad für Echtzeitreaktion: CRM-Updates, Live-Zähler, ereignisgesteuerte Workflows. Die Latenz ist niedrig; der operative Overhead ist handhabbar, wenn Sie bereits über eingehende HTTPS-Infrastruktur verfügen.
Verwenden Sie Polling als wöchentlichen oder täglichen Abgleichsdurchlauf: Ziehen Sie die vollständige Zeitreihe für die vergangene Woche, vergleichen Sie die Summen mit dem, was Ihr Webhook-Handler aufgezeichnet hat, und identifizieren Sie Lücken. Dies fängt Zustellungen ab, die ihr Retry-Fenster während eines Ausfalls erschöpft haben, Ereignisse, die in falscher Reihenfolge eintrafen, und jede Diskrepanz zwischen Ihrem lokalen Zustand und Elidos Source of Truth.
Die Analytics-API ist für diese Rolle gut geeignet. Der /summary-Endpoint gibt aggregierte Summen für einen Datumsbereich in einer einzigen Abfrage zurück; der /timeseries-Endpoint gibt tägliche Buckets zurück. Ein Abgleichs-Job, der einmal pro Nacht läuft und die in Ihrem CRM aufgezeichneten Klickzähler mit der API-Zusammenfassung für dasselbe Fenster vergleicht, kann Datenintegritätsprobleme aufdecken, bevor sie zu kundenseitig sichtbaren Problemen werden.
Ein Polling-Cron in Python#
Für Teams, die mit Polling beginnen und später zu Webhooks aufsteigen wollen, ist hier eine minimale Implementierung mit der schedule-Bibliothek, die /clicks/recent in einem Fünf-Minuten-Intervall aufruft:
import schedule
import time
import requests
import os
API_BASE = "https://api.elido.app/v1/analytics"
WORKSPACE_ID = os.environ["ELIDO_WORKSPACE_ID"]
API_KEY = os.environ["ELIDO_API_KEY"]
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Track the cursor across poll intervals so we only fetch new clicks
_cursor = None
def poll_recent_clicks():
global _cursor
params = {"limit": 100}
if _cursor:
params["cursor"] = _cursor
while True:
resp = requests.get(
f"{API_BASE}/workspaces/{WORKSPACE_ID}/clicks/recent",
headers=HEADERS,
params=params,
timeout=10,
)
resp.raise_for_status()
body = resp.json()
items = body.get("items", [])
for click in items:
process_click(click)
next_cursor = body.get("next_cursor")
if not next_cursor:
# Persist the current cursor for the next run
if items:
_cursor = None # reset: next poll fetches from now
break
params["cursor"] = next_cursor
def process_click(click: dict):
# Replace with your actual processing logic
print(f"click: link={click['link_id']} country={click.get('country_code')}")
schedule.every(5).minutes.do(poll_recent_clicks)
if __name__ == "__main__":
poll_recent_clicks() # run once on startup to catch up
while True:
schedule.run_pending()
time.sleep(10)
In einem Produktions-Deployment ersetzen Sie das print durch Ihre eigentliche Senke - ein Datenbankschreibvorgang, ein CRM-API-Aufruf, eine Message-Queue-Veröffentlichung - und fügen eine Fehlerbehandlung mit exponentiellem Backoff um den requests.get-Aufruf hinzu.
Bot-Filterung und was sie für Ihre Integration bedeutet#
Ein Detail, das beide Muster betrifft: Elidos edge-redirect-Service filtert Bot-Klicks heraus, bevor er Klickereignisse an die Verarbeitungspipeline emittiert. Anfragen von Googlebot, Bingbot, Slackbot, Uptime-Monitoren, curl, Skriptbibliotheken und leeren User-Agents produzieren keine click.recorded-Ereignisse und erscheinen nicht in den Ergebnissen der Analytics-API.
Das ist wichtig, weil es bedeutet, dass Ihr Webhook-Handler oder Polling-Job mit menschlichen Redirect-Zählern arbeitet, nicht mit rohen HTTP-Request-Zählern. Wenn Sie Elido-Klickdaten gegen serverseitige Metriken korrelieren - die Server-Logs Ihrer Anwendung, die Zugriffslogs eines CDN - erwarten Sie, dass Elidos Zahlen niedriger sind. Die Diskrepanz ist kein Bug; es ist der Bot-Filter, der das Rauschen entfernt, bevor es Sie erreicht.
Für mehr Details darüber, was der Bot-Filter abdeckt und wie der Verdachts-Scorer Grenzverkehr markiert, hat der Analytics-Leitfaden eine vollständige Aufschlüsselung. Für die Sicherheitseigenschaften des Webhook-Signaturschemas - einschließlich des HMAC-Formats, der Zeitstempel-Bindung und dessen, was es verhindert - siehe die Sicherheits-Checkliste.
Die Preisseite enthält die Aufschlüsselung, welche Plan-Tiers Webhook-Endpoints enthalten und bei welchen Zustellungsvolumen-Obergrenzen.
Verwandtes im Blog#
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.
Kostenlos, keine Anmeldung erforderlich · 2 pro Tag