Elidos URL-Shortener-Webhooks posten ein signiertes JSON-Envelope an deinen HTTPS-Endpunkt, sobald sich etwas in einem Workspace ändert: ein Link wird erstellt, bearbeitet, gelöscht, läuft ab oder erreicht sein Klicklimit, ein Mitglied wird eingeladen, eine Domain wird verifiziert. Jede Anfrage trägt einen Header X-Webhook-Signature: v1=<hex>, der HMAC-SHA256 über {timestamp}.{raw_body} ist, und eine fehlgeschlagene Zustellung erhält drei Versuche in etwa zwanzig Minuten.
Was heute nicht gesendet wird, ist ein Webhook für Link-Klicks. Klicks laufen stattdessen über die Analytics-API und die Event-Forwarder, und ich zeige am Ende, wo. Dieser Beitrag behandelt die ausgehende Hälfte der API-Oberfläche; der Schnelleinstieg URL-Shortener-API + SDKs behandelt die eingehende Hälfte, und Smart Links erklärt ist der Features-Cornerstone, aus dem die Link-Ereignisse stammen.
Welche Link-Ereignisse heute einen Webhook auslösen
Jedes Ereignis unten erreicht einen Webhook-Endpunkt, der es namentlich abonniert hat. Das Formular für neue Endpunkte im Dashboard bietet Checkboxen für die acht gängigsten; die API akzeptiert jeden Namen aus der Liste.
| Ereignis | Feuert wenn | Dashboard-Checkbox |
|---|---|---|
link.created | Ein Link wird erstellt, einzeln oder per Bulk-Import | ja |
link.updated | Ziel, Einstellungen oder Status ändern sich, Bulk-Edits, Wiederherstellungen | ja |
link.deleted | Ein Link wird gelöscht, einzeln oder per Bulk | ja |
link.expired | Ein Link überschreitet sein Ablaufdatum | nur API |
link.cap_reached | Ein Link erreicht seine maximale Klickanzahl | nur API |
link.broken | Die Broken-Link-Prüfung erkennt ein fehlerhaftes Ziel | nur API |
workspace.created, workspace.updated | Ein Workspace wird bereitgestellt oder seine Einstellungen ändern sich | ja |
member.invited, member.removed | Ein Mitglied wird hinzugefügt (direkt, per SCIM oder angenommene Einladung) oder entfernt | ja |
member.role_changed | Die Rolle eines Mitglieds ändert sich | nur API |
invitation.created, invitation.accepted | Eine Einladung wird gesendet oder angenommen | nur API |
domain.verified, domain.ssl_failed | Eine eigene Domain besteht die DNS-Prüfungen oder scheitert nach 24 Stunden weiterhin daran | nur API |
audit.event | Jeder Audit-Log-Eintrag | ja |
Verschlüsselte Links fügen zwei weitere hinzu, link.encrypted_created und link.encryption_rotated. Wenn du die Liste nicht von Hand aktuell halten möchtest, erstelle einen siem-Endpunkt: Er erhält jedes Ereignis im Workspace, Audit-Einträge eingeschlossen, ganz ohne Abonnement-Filter.
Auf der Roadmap, aber noch nicht live: click.created (ein gesampelter Klick-Stream), Abrechnungsereignisse wie billing.subscription_upgraded, und Filter pro Endpunkt wie "nur Links im Ordner X". Abonniere diese Namen heute noch nicht; niemand veröffentlicht sie.
Einen Webhook-Endpunkt mit der API erstellen
Ein Endpunkt gehört zu einem Workspace. Du erstellst ihn mit einem POST an /v1/workspaces/{workspace_id}/webhooks:
curl -X POST "https://api.elido.app/v1/workspaces/$WORKSPACE_ID/webhooks" \
-H "Authorization: Bearer elido_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/elido",
"events": ["link.created", "link.updated", "link.deleted"],
"description": "CRM sync",
"kind": "event"
}'
Die Antwort ist 201 mit dem Endpunkt und, für die Arten event und siem, einem einmaligen Secret:
{
"endpoint": {
"id": 7,
"workspace_id": 42,
"url": "https://hooks.example.com/elido",
"events": ["link.created", "link.updated", "link.deleted"],
"is_active": true,
"description": "CRM sync",
"kind": "event",
"config": {},
"created_at": "2026-09-21T09:12:44Z"
},
"secret": "whsec_9f2c..."
}
Elido generiert das Secret; du sendest keines. Kopiere es jetzt, denn kein späterer Aufruf gibt es zurück. kind nimmt fünf Werte an. event und siem sind die signierten JSON-Zustellungen, die dieser Beitrag behandelt. discord, telegram und sentry formen dieselben Ereignisse zu einer Chat-Nachricht oder einem Sentry-Ereignis um, authentifizieren sich über die URL oder ein verschlüsseltes Bot-Token und tragen keine HMAC-Header.
Die Berechtigungen haben sich diesen Monat geändert. Endpunkte lesen und das Zustelllog einsehen erfordert workspace.view. Erstellen, bearbeiten, löschen, ein Secret rotieren oder eine Zustellung erneut senden erfordert workspace.edit, was Admin oder Owner bedeutet. Ein API-Schlüssel funktioniert innerhalb des Workspace, für den er ausgestellt wurde, und nie über die Rolle hinaus, die bei seiner Erstellung gewählt wurde - ein Schlüssel mit viewer-Rolle bekommt beim obigen POST also ein 403. Vollständige Referenz: die Webhook-Doku.
Das Webhook-Payload-Envelope
Jede signierte Zustellung hat dasselbe vierteilige Envelope. data enthält den geänderten Datensatz, bei Link-Ereignissen also die Link-Zeile:
{
"type": "link.created",
"workspace_id": 42,
"data": {
"id": 91834,
"workspace_id": 42,
"domain_id": 3,
"slug": "spring-sale",
"destination_url": "https://shop.example.com/spring",
"title": "Spring sale landing",
"tags": ["newsletter"],
"status": "active",
"expires_at": null,
"max_clicks": null,
"redirect_status": 302,
"created_by_user_id": 17,
"created_at": "2026-09-21T09:14:02.184311Z"
},
"timestamp": "2026-09-21T09:14:02Z"
}
Dieses Beispiel ist gekürzt; das echte data trägt jede Spalte des Links, einschließlich Targeting-Regeln, Ordner, Kampagne und Scan-Felder. Es gibt keine Ereignis-ID und keine short_url im Body, also baue die Short URL aus deiner Domain und slug zusammen, falls du sie brauchst. Geplante Ereignisse senden stattdessen ein kleineres Objekt: link.expired hat link_id, slug und destination_url, und link.cap_reached fügt cap und clicks hinzu.
Ein Fix, den du kennen solltest, falls du vor dieser Woche Payloads geloggt hast: Geheime Felder werden jetzt entfernt, bevor ein Payload Elido verlässt. Der password_hash eines passwortgeschützten Links und der token einer Einladung tauchten früher in data auf; das tun sie nicht mehr, weder in neuen Zustellungen noch im Zustelllog. Falls du alte Payloads gespeichert hast, lohnt es sich, diese Daten zu löschen.
Den X-Webhook-Signature-Header verifizieren
Jede signierte Anfrage trägt diese Header:
X-Webhook-Signature: v1=5d8f0c3e...
X-Elido-Signature: v1=5d8f0c3e...
X-Webhook-Timestamp: 1790068442
X-Webhook-Event: link.created
X-Webhook-Delivery: 55120
User-Agent: Elido-Webhooks/1.0
Die beiden Signatur-Header tragen denselben Wert. Elido berechnet HMAC-SHA256, wie in RFC 2104 definiert, mit dem gesamten String whsec_... als Schlüssel, über den Zeitstempel, einen Punkt und die rohen Body-Bytes. Der Hex-Digest erhält ein Präfix v1=. Es gibt kein t=-Feld innerhalb des Headers; der Zeitstempel steht in seinem eigenen Header.
In Node signierst du die rohen Bytes, nicht ein neu serialisiertes Objekt. Express braucht deshalb express.raw({ type: "application/json" }) auf dieser Route:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyElido(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"));
// During a rotation the old secret signs X-Elido-Signature-Previous.
return ["x-elido-signature", "x-elido-signature-previous"].some((name) => {
const got = Buffer.from(headers[name] ?? "");
return got.length === expected.length && timingSafeEqual(got, expected);
});
}
Die Längenprüfung ist wichtig: Nodes timingSafeEqual wirft bei unterschiedlich großen Buffern einen Fehler, statt false zurückzugeben. Die Python-Version mit dem Standardmodul hmac:
import hashlib
import hmac
import time
def verify_elido(secret: str, headers, raw_body: bytes, tolerance: int = 300) -> bool:
ts = headers.get("X-Webhook-Timestamp", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > tolerance:
return False
digest = hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256)
expected = "v1=" + digest.hexdigest()
for name in ("X-Elido-Signature", "X-Elido-Signature-Previous"):
got = headers.get(name)
if got and hmac.compare_digest(got, expected):
return True
return False
Wenn Ihre Prüfung weiterhin fehlschlägt, enthält der Leitfaden zur Verifizierung von Webhook-Signaturen eine Go-Version, einen n8n-Code-Knoten und die üblichen Ursachen für eine Abweichung. Das Fünf-Minuten-Fenster ist deine eigene Prüfung, nicht unsere. Elido stempelt bei jedem Versuch, auch bei Retries, einen frischen Zeitstempel, sodass ein legitimer Retry nie veraltet aussieht. Ohne das Fenster könnte jeder, der eine Anfrage abgefangen hat, sie nächste Woche wiederholen, und die Signatur würde immer noch passen.
Rotation läuft über POST /v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret oder den Rotate-Button auf der Endpunktseite. Du bekommst das neue Secret einmal. Für die nächsten sieben Tage trägt jede Zustellung zusätzlich X-Elido-Signature-Previous, signiert mit dem alten Secret, weshalb beide Funktionen oben es probieren. Setze das neue Secret jederzeit innerhalb dieser Woche ein, ohne dass etwas fehlschlägt.
Die Webhook-Retry-Policy
Der Zustell-Worker nimmt sich alle paar Sekunden anstehende Zustellungen vor, sodass eine Link-Änderung dich meist innerhalb von Sekunden erreicht. Jede 2xx-Antwort markiert die Zustellung als erledigt. Ein Nicht-2xx-Status, ein Netzwerkfehler oder keine Antwort innerhalb von 10 Sekunden gilt als fehlgeschlagener Versuch.
Drei Versuche pro Zustellung, zwanzig Minuten insgesamt. Das ist bewusst kurz, und ehrlich gesagt kürzer, als ich es für einen Empfänger hinter einem wackeligen VPN wählen würde. Ein zweistündiger Ausfall auf deiner Seite wird von automatischen Retries nicht abgedeckt. Was das abdeckt, ist das Zustelllog: GET /v1/workspaces/{workspace_id}/webhooks/{id}/deliveries listet jede Zustellung mit Status, HTTP-Code, Latenz, Versuchszahl und nächstem Retry-Zeitpunkt, und die Endpunktseite zeigt dieselben Zeilen mit einem Retry-Button. Retry, oder POST .../deliveries/{delivery_id}/retry, rüstet eine fehlgeschlagene oder zugestellte Zustellung mit einem frischen Budget von drei Versuchen wieder scharf und liefert 202. Eine noch ausstehende Zustellung bekommt 409.
Manche Fehlschläge überspringen die Retries. Ein Telegram-Endpunkt ohne chat_id, oder ein fehlerhaftes Sentry-DSN, wird sofort als fehlgeschlagen markiert, weil ein Wiederholen der Anfrage die Konfiguration nicht repariert. Um einen Endpunkt offline zu nehmen, ohne ihn zu löschen, sende PUT mit "is_active": false; pausierte Endpunkte bekommen keine neuen Zustellungen.
Wenn dein Handler schwere Arbeit erledigt, gib zuerst 200 zurück und stelle den Job in eine Queue. Die Zehn-Sekunden-Grenze ist der Punkt, an dem ein langsamer, aber erfolgreicher Handler zu einem Duplikat wird - womit wir bei der Deduplizierung sind.
Planst du einen Empfänger für dein Team? Die Webhook-Feature-Seite zeigt die Dashboard-Seite von all dem.
Webhook-Idempotenz und Reihenfolge
Elido liefert at-least-once. Der Retry-Abschnitt zeigte einen Weg, wie ein Duplikat entsteht: Dein Handler committet die Arbeit, verpasst dann das Zehn-Sekunden-Fenster, und Elido sendet es erneut. Ein manueller Retry sendet absichtlich erneut.
Beide Fälle behalten denselben X-Webhook-Delivery-Wert, weil er die ID einer Zustellung an einen Endpunkt ist, nicht eines Versuchs. Indiziere darauf:
CREATE TABLE elido_webhook_seen (
delivery_id BIGINT PRIMARY KEY,
received_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- in the handler, inside the same transaction as your work:
INSERT INTO elido_webhook_seen (delivery_id) VALUES ($1)
ON CONFLICT (delivery_id) DO NOTHING
RETURNING delivery_id;
-- no row back means you've already handled this delivery
Zwei Endpunkte, die dasselbe Ereignis abonniert haben, bekommen zwei verschiedene Zustell-IDs - dedupliziere also pro Endpunkt. In seltenen Neustarts auf unserer Seite kann ein Ereignis zweimal als separate Zustellungen eingereiht werden; wenn ein doppelter Schreibvorgang schaden würde, füge eine zweite Absicherung auf type plus data.id plus data.updated_at hinzu.
Es gibt kein Reihenfolge-Versprechen. Zustellungen gehen in der Reihenfolge fälligster-zuerst raus, aber ein Retry eines früheren Ereignisses kann nach einem späteren ankommen. Vergleiche data.updated_at mit dem, was du gespeichert hast, bevor du einen Link überschreibst, und verlasse dich nicht auf den timestamp des Envelopes für die Reihenfolge - er hat nur Sekundenauflösung.
Wo Klickdaten statt eines Klick-Webhooks liegen
Das ist der Teil, den die ältere Version dieses Beitrags falsch hatte. Es gibt heute keinen click-Webhook, und click.created ist geplant, nicht ausgeliefert. Der Redirect-Pfad bleibt frei von synchroner Arbeit, was der Beitrag Fire-and-forget-Klick-Ingestion erklärt, und Klicks gehen in den Analytics-Speicher statt in die Webhook-Queue.
Für Klickdaten auf Klickebene hast du heute zwei Wege:
- Event-Forwarder. Jeder Short-Link-Klick wird zu einem serverseitigen Ereignis in dem Tool, das du bereits nutzt: Mixpanel-Link-Klickereignisse, Klaviyo-Klickereignisse auf Profilen oder Datadog-Redirect-Metriken für Ops-Dashboards.
- Die Analytics-API.
GET /v1/analytics/workspaces/{workspace_id}/clicks/recentliefert aktuelle Klicks, undclicks.csvexportiert sie, sodass ein geplanter Job die benötigten Zeilen abrufen kann.
Welcher Weg passt, hängt von Latenz und davon ab, wo die Daten landen sollen; Webhooks vs. Polling für Click-Tracking geht diese Abwägung durch. Und wenn der gesampelte click.created-Stream ausgeliefert wird, sagt diese Seite es als Erstes.
Lies den Cornerstone: Smart Links erklärt.
Verwandtes im Blog
- Webhooks vs. Polling für Click-Tracking - wann pushen, wann pollen.
- URL-Shortener-API + SDKs-Schnelleinstieg - die eingehende API-Oberfläche.
- Fire-and-forget-Klick-Ingestion - warum Klicks nie auf ausgehende Aufrufe warten.
- Mixpanel-Link-Klickereignisse - Klickdaten als serverseitige Ereignisse.
- Datadog-Link-Redirect-Metriken - Redirect-Gesundheit auf einem Ops-Dashboard.
- Webhook-Signaturen prüfen - HMAC-Prüfungen in Node, Python, Go und n8n sowie die Fehlersuche bei einer Abweichung.
Häufig gestellte Fragen
Sendet Elido bei jedem Link-Klick einen Webhook?
Heute nicht. Webhooks decken Workspace-Änderungen wie link.created, link.updated, link.expired und member.invited ab. Ein click.created-Ereignis ist als gesampelter Stream geplant. Für Klickdaten auf Klickebene nutze bis dahin die Analytics-API oder einen Event-Forwarder wie Mixpanel, Klaviyo oder Datadog.
Wie verifiziere ich eine Elido-Webhook-Signatur?
Berechne HMAC-SHA256 über den Wert von X-Webhook-Timestamp, einen Punkt und den rohen Request-Body, mit deinem whsec_-Secret als Schlüssel. Hex-kodiere das Ergebnis, stelle v1= voran und vergleiche es in konstanter Zeit mit dem Header X-Webhook-Signature. Zeitstempel, die älter als fünf Minuten sind, ablehnen.
Wie oft wiederholt Elido einen fehlgeschlagenen Webhook?
Jede Zustellung erhält drei Versuche: einen sofort, einen fünf Minuten nach einem Fehlschlag und einen fünfzehn Minuten danach. Jeder Nicht-2xx-Status, jeder Netzwerkfehler oder eine Antwort langsamer als zehn Sekunden gilt als Fehlschlag. Nach dem dritten Versuch wird die Zustellung als fehlgeschlagen markiert, bis du auf Wiederholen klickst.
Was ist der Unterschied zwischen X-Webhook-Signature und X-Elido-Signature?
Nichts außer dem Namen. Beide Header tragen dieselbe v1=-Signatur, und X-Webhook-Signature bleibt für ältere Empfänger bestehen. Während einer Secret-Rotation sendet Elido zusätzlich sieben Tage lang X-Elido-Signature-Previous, signiert mit dem alten Secret.
Wie verhindere ich, dass ich denselben Webhook zweimal verarbeite?
Speichere den Wert des Headers X-Webhook-Delivery und überspringe jede Anfrage mit einem Wert, den du bereits verarbeitet hast. Er identifiziert eine Zustellung an einen Endpunkt und bleibt über automatische Retries und manuelle erneute Sendungen hinweg gleich, ein eindeutiger Index darauf genügt also.
Wer kann Webhooks in einem Workspace erstellen oder löschen?
Workspace-Admins und -Owner. Endpunkte auflisten und das Zustelllog lesen erfordert Leserechte; erstellen, bearbeiten, löschen, ein Secret rotieren oder eine Zustellung erneut senden erfordert die Berechtigung workspace.edit. API-Schlüssel sind auf die Rolle beschränkt, die bei der Erstellung des Schlüssels gewählt wurde.
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