9 min czytaniaFunkcje

Webhooki dla zdarzeń linków: ładunki, sygnatury, ponowienia

Webhooki skracacza URL dla zdarzeń linków: prawdziwa koperta ładunku, weryfikacja HMAC X-Webhook-Signature w Node i Pythonie, polityka ponowień i klucze deduplikacji.

Marius Voß
DevRel · edge infra
Diagram w stylu pikselowym webhooków skracacza URL: zdarzenia link.created, link.updated i member.invited przechodzą przez podpisane dostarczenie do punktów końcowych event, siem i discord, pod paskiem z napisem HMAC-SHA256 v1=, 10s timeout, 3 próby

Webhooki skracacza URL Elido wysyłają metodą POST podpisaną kopertę JSON na Twój punkt końcowy HTTPS za każdym razem, gdy coś zmienia się w workspace: link zostaje utworzony, edytowany, usunięty, wygasa lub osiąga swój limit kliknięć, członek zostaje zaproszony, domena zostaje zweryfikowana. Każde żądanie niesie nagłówek X-Webhook-Signature: v1=<hex>, który jest HMAC-SHA256 liczonym po {timestamp}.{raw_body}, a nieudane dostarczenie otrzymuje trzy próby w ciągu około dwudziestu minut.

To, czego nie wysyła dzisiaj, to webhook kliknięcia linku. Kliknięcia wychodzą zamiast tego przez API analityczne i przekaźniki zdarzeń, a pokażę gdzie na końcu. Ten wpis to wychodząca połowa powierzchni API; szybki start API + SDK skracacza URL opisuje połowę przychodzącą, a smart links wyjaśnione to fundament funkcji, z którego pochodzą zdarzenia linków.

Które zdarzenia linków uruchamiają webhook dzisiaj

Każde poniższe zdarzenie trafia do punktu końcowego webhooka, który zasubskrybował je po nazwie. Formularz nowego punktu końcowego w panelu oferuje checkboxy dla ośmiu najczęstszych; API przyjmuje dowolną nazwę z listy.

ZdarzenieKiedy się uruchamiaCheckbox w panelu
link.createdLink zostaje utworzony, pojedynczo lub w imporcie zbiorczymtak
link.updatedZmienia się cel, ustawienia lub status, edycje zbiorcze, przywróceniatak
link.deletedLink zostaje usunięty, pojedynczo lub zbiorczotak
link.expiredLink mija swoją datę wygaśnięciatylko API
link.cap_reachedLink osiąga maksymalną liczbę kliknięćtylko API
link.brokenSprawdzenie uszkodzonych linków wykrywa niedziałający celtylko API
workspace.created, workspace.updatedWorkspace zostaje utworzony lub zmieniają się jego ustawieniatak
member.invited, member.removedCzłonek zostaje dodany (bezpośrednio, przez SCIM lub zaakceptowane zaproszenie) lub usuniętytak
member.role_changedZmienia się rola członkatylko API
invitation.created, invitation.acceptedZaproszenie zostaje wysłane lub zaakceptowanetylko API
domain.verified, domain.ssl_failedDomena niestandardowa przechodzi kontrole DNS albo nadal ich nie przechodzi po 24 godzinachtylko API
audit.eventDowolny wpis w dzienniku audytutak

Zaszyfrowane linki dodają jeszcze dwa, link.encrypted_created i link.encryption_rotated. Jeśli wolisz nie utrzymywać tej listy ręcznie, utwórz punkt końcowy typu siem: otrzymuje on każde zdarzenie w workspace, wraz z wpisami audytu, bez żadnego filtra subskrypcji.

Na mapie drogowej, ale jeszcze nie na żywo: click.created (próbkowany strumień kliknięć), zdarzenia rozliczeniowe takie jak billing.subscription_upgraded oraz filtry per punkt końcowy w rodzaju "tylko linki w folderze X". Nie subskrybuj dziś tych nazw, nic ich jeszcze nie publikuje.

Tworzenie punktu końcowego webhooka przez API

Punkt końcowy należy do jednego workspace. Tworzysz go żądaniem POST do /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"
  }'

Odpowiedzią jest 201 wraz z punktem końcowym oraz, dla rodzajów event i siem, jednorazowym sekretem:

{
  "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 generuje sekret; nie wysyłasz go samodzielnie. Skopiuj go teraz, bo żadne kolejne wywołanie go nie zwróci. kind przyjmuje pięć wartości. event i siem to podpisane dostarczenia JSON opisane w tym wpisie. discord, telegram i sentry przekształcają te same zdarzenia w wiadomość czatu lub zdarzenie Sentry, uwierzytelniają się przez URL lub zaszyfrowany token bota i nie niosą żadnych nagłówków HMAC.

Uprawnienia zmieniły się w tym miesiącu. Odczyt punktów końcowych i dziennika dostarczeń wymaga workspace.view. Tworzenie, edycja, usuwanie, rotacja sekretu lub ponowna wysyłka dostarczenia wymaga workspace.edit, czyli roli administratora lub właściciela. Klucz API działa wewnątrz workspace, dla którego został wydany, i nigdy powyżej roli wybranej w momencie jego utworzenia, więc klucz na poziomie podglądu otrzyma 403 przy powyższym POST. Pełne odniesienie: dokumentacja webhooków.

Koperta ładunku webhooka

Każde podpisane dostarczenie ma tę samą czteropolową kopertę. data zawiera rekord, który się zmienił, więc dla zdarzeń linków jest to wiersz linku:

{
  "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"
}

Ten przykład jest przycięty; prawdziwe data niesie każdą kolumnę linku, wraz z regułami targetowania, folderem, kampanią i polami skanowania. Nie ma tu identyfikatora zdarzenia ani short_url w ciele, więc jeśli go potrzebujesz, zbuduj krótki URL z własnej domeny i slug. Zdarzenia zaplanowane wysyłają mniejszy obiekt: link.expired ma link_id, slug i destination_url, a link.cap_reached dodaje cap i clicks.

Jedna poprawka, o której warto wiedzieć, jeśli logowałeś ładunki przed tym tygodniem: pola z sekretami są teraz usuwane, zanim ładunek opuści Elido. password_hash linku chronionego hasłem i token zaproszenia kiedyś pojawiały się w data; teraz już nie, ani w nowych dostarczeniach, ani w dzienniku dostarczeń. Jeśli przechowujesz stare ładunki, warto je wyczyścić.

Weryfikacja nagłówka X-Webhook-Signature

Każde podpisane żądanie niesie te nagłówki:

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

Oba nagłówki sygnatury niosą tę samą wartość. Elido oblicza HMAC-SHA256, zgodnie z RFC 2104, z całym ciągiem whsec_... jako kluczem, po znaczniku czasu, kropce i surowych bajtach ciała. Skrót hex otrzymuje prefiks v1=. Wewnątrz nagłówka nie ma pola t=; znacznik czasu znajduje się we własnym nagłówku.

Przepływ weryfikacji sygnatury: nagłówki X-Webhook-Signature i X-Webhook-Timestamp wraz z surowym ciałem i sekretem zasilają HMAC-SHA256 po ts kropka body, porównywany w czasie stałym jako v1= hex, następnie 5-minutowa bramka świeżości po stronie odbiornika dzieli na przetwórz zdarzenie albo odrzuć z 400

W Node podpisuj surowe bajty, nie ponownie zserializowany obiekt. Express z tego powodu potrzebuje na tej trasie express.raw({ type: "application/json" }):

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

Sprawdzenie długości ma znaczenie: timingSafeEqual w Node rzuca wyjątek dla buforów o różnych rozmiarach zamiast zwracać false. Wersja w Pythonie ze standardowym modułem 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

Jeśli weryfikacja nadal kończy się niepowodzeniem, poradnik weryfikacji sygnatur webhooków zawiera wersję w Go, węzeł Code w n8n oraz typowe przyczyny niezgodności. Pięciominutowe okno to Twoja kontrola, nie nasza. Elido stempluje świeży znacznik czasu przy każdej próbie, w tym ponowieniach, więc uzasadnione ponowienie nigdy nie wygląda na nieaktualne. Bez tego okna każdy, kto przechwycił jedno żądanie, mógłby powtórzyć je za tydzień, a sygnatura wciąż by pasowała.

Rotacja to POST /v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret albo przycisk Rotate na stronie punktu końcowego. Nowy sekret otrzymujesz raz. Przez kolejne siedem dni każde dostarczenie niesie też X-Elido-Signature-Previous, podpisaną starym sekretem, dlatego obie powyższe funkcje ją próbują. Wdróż nowy sekret w dowolnym momencie tego tygodnia i nic nie zawiedzie.

Polityka ponowień webhooka

Worker dostarczeń odbiera oczekujące dostarczenia co kilka sekund, więc zmiana linku zwykle dociera do Ciebie w ciągu sekund. Dowolna odpowiedź 2xx oznacza dostarczenie jako ukończone. Status inny niż 2xx, błąd sieci lub brak odpowiedzi w ciągu 10 sekund liczy się jako nieudana próba.

Wykres słupkowy harmonogramu ponowień webhooka: próba 1 w T+0, próba 2 pięć minut po niepowodzeniu w T+5m, próba 3 piętnaście minut później w T+20m, następnie dostarczenie zostaje oznaczone jako nieudane bez kolejnych automatycznych prób, dopóki ktoś nie naciśnie Retry albo nie wywoła punktu końcowego retry

Trzy próby na dostarczenie, dwadzieścia minut od początku do końca. To celowo krótko i szczerze mówiąc krócej, niż wybrałbym dla odbiorcy za niestabilnym VPN-em. Dwugodzinna awaria po Twojej stronie nie zostanie pokryta automatycznymi ponowieniami. To, co ją pokrywa, to dziennik dostarczeń: GET /v1/workspaces/{workspace_id}/webhooks/{id}/deliveries wylicza każde dostarczenie ze statusem, kodem HTTP, opóźnieniem, liczbą prób i czasem następnej próby, a strona punktu końcowego pokazuje te same wiersze z przyciskiem Retry. Retry, albo POST .../deliveries/{delivery_id}/retry, uzbraja od nowa nieudane lub dostarczone dostarczenie ze świeżym budżetem trzech prób i zwraca 202. Dostarczenie wciąż oczekujące otrzymuje 409.

Niektóre niepowodzenia pomijają ponowienia. Punkt końcowy Telegram bez chat_id albo źle sformułowany DSN Sentry zostaje od razu oznaczony jako nieudany, bo powtórzenie żądania nie naprawi konfiguracji. Aby wyłączyć punkt końcowy bez jego usuwania, wyślij PUT z "is_active": false; wstrzymane punkty końcowe nie otrzymują nowych dostarczeń.

Jeśli Twój handler wykonuje ciężką pracę, zwróć najpierw 200 i zakolejkuj zadanie. Dziesięciosekundowy limit to miejsce, w którym wolny, ale udany handler zamienia się w duplikat, co prowadzi nas do deduplikacji.

Planujesz odbiornik dla swojego zespołu? Strona funkcji webhooków pokazuje to wszystko od strony panelu.

Idempotencja i kolejność webhooków

Elido dostarcza co najmniej raz. Sekcja o ponowieniach pokazała jeden sposób, w jaki powstaje duplikat: Twój handler zatwierdza pracę, potem nie mieści się w dziesięciosekundowym oknie, a Elido wysyła go ponownie. Ręczne Retry wysyła ponownie celowo.

Oba przypadki zachowują tę samą wartość X-Webhook-Delivery, bo jest to identyfikator jednego dostarczenia do jednego punktu końcowego, a nie jednej próby. Klucz się na niej:

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

Dwa punkty końcowe zasubskrybowane do tego samego zdarzenia otrzymują dwa różne identyfikatory dostarczenia, więc deduplikuj per punkt końcowy. Przy rzadkich restartach po naszej stronie zdarzenie może zostać zakolejkowane dwukrotnie jako osobne dostarczenia; jeśli podwójny zapis by zaszkodził, dodaj drugie zabezpieczenie na type plus data.id plus data.updated_at.

Nie ma tu żadnej gwarancji kolejności. Dostarczenia wychodzą od najstarszych zaległych, ale ponowienie wcześniejszego zdarzenia może dotrzeć po późniejszym. Porównaj data.updated_at z tym, co już zapisałeś, zanim nadpiszesz link, i nie polegaj na timestamp koperty przy ustalaniu kolejności: ma on precyzję tylko do jednej sekundy.

Gdzie znajdują się dane kliknięć zamiast webhooka kliknięcia

To jest część, którą starsza wersja tego wpisu podawała błędnie. Nie ma dziś webhooka click, a click.created jest zaplanowany, ale niewdrożony. Ścieżka przekierowania jest utrzymywana wolna od pracy synchronicznej, co wyjaśnia wpis pozyskiwanie kliknięć w trybie fire-and-forget, a kliknięcia trafiają do magazynu analitycznego, a nie do kolejki webhooków.

Na dane na poziomie kliknięcia masz dziś dwie drogi:

  1. Przekaźniki zdarzeń. Każde kliknięcie krótkiego linku staje się zdarzeniem po stronie serwera w narzędziu, z którego już korzystasz: zdarzenia kliknięć linków w Mixpanel, zdarzenia kliknięć Klaviyo w profilach albo metryki przekierowań Datadog dla dashboardów operacyjnych.
  2. API analityczne. GET /v1/analytics/workspaces/{workspace_id}/clicks/recent zwraca ostatnie kliknięcia, a clicks.csv je eksportuje, więc zaplanowane zadanie może pobrać potrzebne wiersze.

To, co pasuje, zależy od opóźnienia i miejsca, w którym dane mają wylądować; webhooki kontra odpytywanie dla śledzenia kliknięć przeprowadza przez ten kompromis. A jeśli próbkowany strumień click.created zostanie wdrożony, ta strona powie o tym pierwsza.

Przeczytaj fundament: smart links wyjaśnione.

Powiązane na blogu

Najczęściej zadawane pytania

Czy Elido wysyła webhook dla każdego kliknięcia linku?

Nie dzisiaj. Webhooki obejmują zmiany w workspace, takie jak link.created, link.updated, link.expired i member.invited. Zdarzenie click.created jest na mapie drogowej jako próbkowany strumień. Na dane na poziomie kliknięcia już teraz skorzystaj z API analitycznego lub przekaźnika zdarzeń, takiego jak Mixpanel, Klaviyo czy Datadog.

Jak zweryfikować sygnaturę webhooka Elido?

Oblicz HMAC-SHA256 na wartości X-Webhook-Timestamp, kropce i surowym ciele żądania, kluczowany Twoim sekretem whsec_. Zakoduj to w hex, dodaj prefiks v1= i porównaj w czasie stałym z nagłówkiem X-Webhook-Signature. Odrzucaj znaczniki czasu starsze niż pięć minut.

Ile razy Elido ponawia nieudany webhook?

Każde dostarczenie ma trzy próby: jedną od razu, jedną pięć minut po niepowodzeniu i jedną piętnaście minut po tamtej. Każdy status inny niż 2xx, błąd sieci lub odpowiedź wolniejsza niż dziesięć sekund liczy się jako niepowodzenie. Po trzeciej próbie dostarczenie zostaje oznaczone jako nieudane, dopóki nie naciśniesz Retry.

Jaka jest różnica między X-Webhook-Signature a X-Elido-Signature?

Żadna poza nazwą. Oba nagłówki niosą tę samą sygnaturę v1=, a X-Webhook-Signature pozostaje ze względu na starsze odbiorniki. Podczas rotacji sekretu Elido wysyła też X-Elido-Signature-Previous, podpisaną starym sekretem, przez siedem dni.

Jak przestać przetwarzać ten sam webhook dwa razy?

Zapisz wartość nagłówka X-Webhook-Delivery i pomiń każde żądanie, którego wartość już obsłużyłeś. Identyfikuje ona jedno dostarczenie do jednego punktu końcowego i pozostaje taka sama przy automatycznych ponowieniach i ręcznych wysyłkach, więc unikalny indeks na niej wystarczy.

Kto może tworzyć lub usuwać webhooki w workspace?

Administratorzy i właściciele workspace. Wyświetlanie punktów końcowych i odczyt dziennika dostarczeń wymaga dostępu do podglądu; tworzenie, edycja, usuwanie, rotacja sekretu lub ponowna wysyłka dostarczenia wymaga uprawnienia workspace.edit. Klucze API są ograniczone do roli wybranej w momencie utworzenia klucza.

Wypróbuj Elido

Wklej URL, otrzymaj krótki link

Bez rejestracji. Link działa 30 dni. Zarejestruj się, aby zachować go na zawsze.

Za darmo, bez rejestracji · 2 dziennie

Wypróbuj Elido

Skracarka URL hostowana w UE: własne domeny, głęboka analityka i otwarte API. Darmowy plan - bez karty kredytowej.

Tagi
url shortener webhooks
link click webhook
webhook signature verification
webhook retry policy
webhook idempotency
webhook payload

Czytaj dalej