8 min czytaniaIntegracje

Webhooki a polling: webhooki śledzenia kliknięć

Webhooki a polling przy śledzeniu kliknięć: opóźnienia i liczba żądań, retry, idempotencja i podpisy, plus tabela decyzji i kod Node, który uruchomiliśmy.

Sasha Ehrlich
Compliance · EU residency

Używaj webhooków, gdy człowiek lub workflow musi zareagować w ciągu sekund, a polling wybierz, gdy kilka minut opóźnienia nie szkodzi i wolisz nie utrzymywać publicznego endpointu. W Elido ten wybór ma haczyk: webhooków śledzenia kliknięć jeszcze nie ma. Webhooki obejmują zdarzenia linków, domen, członków i workspace'ów, a zdarzenie click.created na każde kliknięcie jest w planach, ale nie jest emitowane. Dziś dane o kliknięciach pochodzą z pollingu API analityki.

Ten wpis liczy opóźnienia i wolumen żądań, opisuje, co gwarantuje każdy wzorzec (retry, duplikaty, kolejność, podpisy) i kończy się tabelą decyzji. Kod został uruchomiony w Node 22 na lokalnych mockach, zanim trafił do tekstu. Pełny katalog zdarzeń linków znajdziesz w webhookach dla zdarzeń linków; endpointy kliknięć opisuje przewodnik po API analityki linków.

Webhooki a polling: po jednym akapicie

Polling oznacza, że Twój kod prosi o nowe dane według harmonogramu. Zadanie cron wywołuje GET /v1/workspaces/{workspace_id}/analytics/clicks/recent, obsługuje to, co nowe, i śpi. Każde żądanie inicjujesz Ty. Publiczny URL nie jest potrzebny.

Webhooki odwracają kierunek. Elido wysyła podpisany POST z kopertą JSON na Twój endpoint HTTPS, gdy zdarzenie się wyzwoli, a odpowiedź 2xx oznacza zakończone dostarczenie. Potrzebujesz osiągalnego endpointu. TLS. Handlera, który znosi duplikaty. Dziś zdarzenia to link.created, link.updated, link.expired, domain.verified i reszta listy w dokumentacji webhooków.

Diagram obok siebie: w pollingu Twoje zadanie cron wysyła GET do API Elido i co pięć minut pobiera wiersze; w webhookach serwer Elido wypycha POST na Twój endpoint HTTPS, który zwraca 2xx, a zdarzenie click.created na każde kliknięcie jest wciąż w planach. Strona Webhooki w Elido z listą endpointu, jego subskrybowanych zdarzeń linków, aktywnym statusem i datą utworzenia

Rachunek opóźnień i kosztów

Opóźnienie pollingu wyznacza interwał. Przy interwale I minut zdarzenie czeka średnio I/2 minuty, a w najgorszym razie I minut, plus opóźnienie, z jakim dane stają się dostępne do zapytania. Żądań dziennie jest 1440 / I.

  • 1 minuta: 1440 żądań dziennie, średnie opóźnienie 30 sekund, w najgorszym razie 1 minuta.
  • 5 minut: 288 żądań, średnio 2,5 minuty, w najgorszym razie 5 minut.
  • 15 minut: 96 żądań, średnio 7,5 minuty, w najgorszym razie 15 minut.
  • 60 minut: 24 żądania, średnio 30 minut, w najgorszym razie 60 minut.

Koszt widać w zmarnowanych żądaniach, a nie w presji limitów. Opublikowany limit workspace'u w planie Free to 10 żądań na sekundę, czyli 864 000 dziennie (zobacz limity API i idempotencję). Jeden poller z interwałem 1-minutowym zużywa 1440 / 864 000 = 0,17% tego limitu. Na limity natrafisz, gdy odpytujesz osobno każdy z setek linków albo gdy inna automatyzacja dzieli workspace, a nie przy jednym feedzie.

Liczy się marnotrawstwo. Załóżmy, że workspace zmienia 40 linków dziennie, a Ty obserwujesz to pollingiem co minutę. Co najwyżej 40 z 1440 żądań zwraca coś nowego, więc co najmniej 1400 (97%) jest pustych. Odbiorca webhooków dostaje 40 żądań dla tych samych 40 zdarzeń. W najgorszym razie, przy trzech próbach na każde, to 120.

Webhooki wygrywają opóźnieniem, które zwykle wynosi sekundy, bo worker dostarczający stale podejmuje oczekujące wiersze. Przegrywają tym, co polling dostaje za darmo: pominięty przebieg naprawia kolejny, bo dane wciąż są w API. Pominięty webhook naprawisz tylko wtedy, gdy zbudowałeś coś do naprawy.

Gwarancje dostarczenia: co obiecuje Elido

Te zachowania wynikają z kodu dostarczania i dokumentacji pomocy, sprawdzonych 1 października 2026.

Retry. Dostarczenie dostaje trzy próby: jedną od razu, jedną 5 minut po niepowodzeniu i jedną 15 minut później, łącznie około 20 minut. Każdy status inny niż 2xx, błąd sieci lub odpowiedź wolniejsza niż 10 sekund to niepowodzenie. Po trzeciej próbie dostarczenie jest oznaczane jako nieudane; Retry w dashboardzie lub POST .../deliveries/{delivery_id}/retry uzbraja je od nowa ze świeżym budżetem trzech prób. Dla porównania, Stripe ponawia zdarzenia w trybie live nawet przez trzy dni. Dwadzieścia minut to mało. Nie zakładaj, że awaria odbiorcy zostanie pokryta.

Idempotencja. Dostarczanie działa w modelu co najmniej raz. Nagłówek X-Webhook-Delivery identyfikuje jedno dostarczenie do jednego endpointu i nie zmienia się przy ponowieniach automatycznych ani ręcznych, więc to Twój klucz deduplikacji.

Kolejność. Nie ma obietnicy kolejności. Ponowienie wcześniejszego zdarzenia może dotrzeć po późniejszym, więc porównaj data.updated_at z tym, co zapisałeś, zanim cokolwiek nadpiszesz.

Podpisy. Każde podpisane dostarczenie niesie X-Webhook-Signature (alias X-Elido-Signature) z v1= plus szesnastkowy HMAC-SHA256 z {timestamp}.{raw_body}, a znacznik czasu w X-Webhook-Timestamp. Znacznik czasu jest świeży przy każdej próbie, więc prawidłowe ponowienie nigdy nie jest nieaktualne. Podczas rotacji sekretu X-Elido-Signature-Previous niesie przez siedem dni podpis starym sekretem. Weryfikacja podpisów webhooków opisuje Pythona, Go i debugowanie niezgodności.

Transport i wstrzymywanie. Nowe endpointy zdarzeń muszą używać https; zwykły http jest dopuszczony tylko dla localhost i 127.0.0.1. Ustawienie is_active na false wstrzymuje endpoint. Od poprawki z 22 września zatrzymuje to także dostarczenia w kolejce.

Odbiorca webhooków w Node

Ten odbiorca weryfikuje podpis na surowych bajtach, wymusza okno pięciu minut, akceptuje nagłówek poprzedniego sekretu podczas rotacji i deduplikuje po identyfikatorze dostarczenia. Pięciominutowa tolerancja to Twój wybór, a nie Elido, i odpowiada domyślnej wartości Stripe. Uruchomiłem go na lokalnym podpisującym: prawidłowe żądanie zwróciło 200, powtórka z tym samym identyfikatorem dostarczenia zwróciła "duplicate", a zły sekret i znacznik czasu sprzed 400 sekund zwróciły 401.

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

Sprawdzenie długości przed timingSafeEqual ma znaczenie. Node rzuca wyjątek dla buforów o różnych rozmiarach. Najpierw odpowiedz, potem pracuj. Limit 10 sekund to moment, w którym wolny, ale udany handler staje się duplikatem.

Poller kliknięć w Node

Dopóki click.created nie zostanie wdrożone, to jest ścieżka kliknięć. clicks/recent zwraca najnowsze najpierw, przyjmuje limit do 500 (domyślnie 100) i zwraca next_cursor, gdy strona wraca pełna. Każdy przebieg czyta od góry i zatrzymuje się, gdy dojdzie do najnowszego kliknięcia, które już obsłużył. Uruchomiłem go na mocku, który stronicuje po dwa wiersze: pierwszy przebieg obsłużył 5 kliknięć, drugi tylko 2 nowe, trzeci żadnego.

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

Dwa ograniczenia, powiedziane wprost. Kursor porządkuje według znacznika czasu i identyfikatora linku, więc dwa kliknięcia w jeden link w tej samej sekundzie mają wspólny klucz; jeśli nie stać Cię na rzadkie pominięte lub powtórzone kliknięcie, uczyń handle idempotentnym względem pełnego wiersza. Oraz lastSeen żyje w pamięci, więc zapisuj go między restartami, bo inaczej restart odtworzy niedawną historię.

Nie jesteś gotowy budować żadnego z nich? To w porządku. Otwórz dashboard i najpierw skorzystaj z analityki w dashboardzie, a API dodaj, gdy będziesz wiedzieć, jakich liczb potrzebujesz według harmonogramu.

Tabela decyzji

SytuacjaUżyj dziśDlaczego
Reakcja na zmiany linków lub domen w sekundyWebhook na link.*, domain.*Zdarzenia są wypychane; zdarzenie kliknięcia nie jest potrzebne
Licznik kliknięć na żywo w Twoim produkciePolling clicks/recent co 1 minutęBrak jeszcze webhooka kliknięć; 1440 żądań dziennie mieści się w limicie
Wzbogacanie CRM o każde kliknięcieForwarder zdarzeń lub krótki pollingOpóźnienie pollingu średnio 30 sekund zwykle jest akceptowalne
Raportowanie tygodniowe lub miesięcznePolling summary i timeseriesDane retrospektywne nic nie zyskują na push
Brak dostępnego publicznego endpointuPollingWebhooki potrzebują adresu https osiągalnego z internetu
Nie można stracić zdarzenia linkuWebhook plus nocne uzgadnianieRetry pokrywają około 20 minut, polling pokrywa resztę

Gdy ruszy próbkowany strumień click.created, wiersze licznika na żywo i CRM przejdą do kolumny webhooków, a polling stanie się przebiegiem uzgadniającym. Do tego czasu forwardery zdarzeń, takie jak zdarzenia kliknięć linków w Mixpanel, są opcją w stylu push dla danych o kliknięciach.

Wzorzec hybrydowy

Dla wszystkiego, czego nie możesz stracić, uruchom oba. Webhook to szybka ścieżka. Zaplanowany polling porównuje sumy i znajduje luki: dostarczenia, które wyczerpały trzy próby podczas awarii, albo zdarzenia, które dotarły w złej kolejności.

Raporty summary i timeseries zwracają sumy i dzienne koszyki dla zakresu dat w jednym wywołaniu, co czyni nocne porównanie tanim. Pamiętaj, że filtr botów usuwa crawlery i monitory, zanim kliknięcia zostaną zapisane, więc Twoje sumy nie zgodzą się z surowymi logami serwera; przewodnik po analityce wyjaśnia, co jest wykluczane. Resztę opisuje lista kontrolna bezpieczeństwa. Jeśli projektujesz własne webhooki, specyfikacja Standard Webhooks to przydatne źródło.

Strona dokumentacji API w dashboardzie Elido z odnośnikami do referencji API, SDK, specyfikacji OpenAPI i zarządzania kluczami API

Ekran dokumentacji API w workspace'ie DEMO z danymi przykładowymi; to nie jest konto klienta.

Lista kontrolna przed uruchomieniem dla obu wzorców

Zanim wdrożysz odbiornik lub poller, przejdź sześć kontroli. Większość incydentów produkcyjnych przy obu wzorcach bierze się z pominięcia którejś z nich.

  • Webhook: odpowiadaj 2xx w ciągu 10 sekund, a wolną pracę wykonuj po odpowiedzi.
  • Webhook: zweryfikuj podpis z surowego ciała, zanim sparsujesz JSON.
  • Webhook: zapisuj X-Webhook-Delivery z unikalnym indeksem, by ponowienie nie podwoiło liczenia.
  • Poller: zachowuj kursor lub ostatnio widziany klucz poza pamięcią procesu.
  • Poller: przy 429 lub 5xx stosuj wydłużanie przerw zamiast ponawiać w normalnym interwale.
  • Oba: alarmuj, gdy przez dłużej niż zwykła cisza nie przyszło żadne zdarzenie ani nowe kliknięcie, bo cisza to sposób, w jaki wygląda zepsuta integracja. Niezależne opracowania, takie jak poradnik Hookdeck o webhookach i pollingu API, dochodzą do tej samej rady: push dla szybkości, polling do uzgadniania.

Powiązane na blogu

Najczęściej zadawane pytania

Czym różni się webhook od pollingu?

W pollingu Twój kod pyta API o nowe dane według harmonogramu. W webhookach serwer wysyła HTTP POST na Twój endpoint, gdy coś się wydarzy. Polling kosztuje żądania i dodaje do jednego interwału opóźnienia. Webhooki kosztują stale dostępny publiczny endpoint i pracę nad obsługą ponowień i duplikatów.

Czy Elido ma webhooki śledzenia kliknięć?

Jeszcze nie. Webhooki obejmują zdarzenia linków, domen, członków i workspace'ów, takie jak link.created i link.expired. Zdarzenie click.created jest w planach, ale dziś nic go nie publikuje. Dla danych na poziomie kliknięć odpytuj raport clicks/recent API analityki z jego kursorem albo użyj forwardera zdarzeń.

Jak często odpytywać API analityki?

Dopasuj interwał do decyzji, którą zasilają dane. Jedna minuta pasuje do licznika prawie na żywo, od pięciu do piętnastu minut do dashboardów, a godzina lub doba do raportów. Polling co minutę to 1440 żądań dziennie, a co 5 minut to 288, więc krótszy rzadko się opłaca.

Czy webhooki mają gwarancję dostarczenia dokładnie raz?

Nie. Dostarczanie webhooków działa w modelu co najmniej raz, więc odbiorca widzi duplikaty po przekroczeniu czasu lub ręcznym ponownym wysłaniu. Elido zachowuje tę samą wartość X-Webhook-Delivery przy ponowieniach, więc zapisz ten identyfikator i pomijaj wszystko, co już obsłużyłeś.

Co się stanie, gdy mój endpoint webhooka nie działa?

Elido wykonuje trzy próby: jedną natychmiast, jedną pięć minut po niepowodzeniu i jedną piętnaście minut później. Jeśli wszystkie trzy zawiodą, dostarczenie jest oznaczane jako nieudane, dopóki ktoś nie naciśnie Retry lub nie wywoła endpointu retry. Awarie dłuższe niż około dwudziestu minut wymagają zadania uzgadniającego.

Czy mogę połączyć webhooki z pollingiem?

Tak, a w przypadku wszystkiego, co ważne, powinieneś. Użyj webhooka jako szybkiej ścieżki, a zaplanowanego pollingu jako siatki bezpieczeństwa, która znajduje zdarzenia pominięte przez odbiorcę. Polling może działać co godzinę lub co noc, bo ma tylko zasypywać luki.

Wypróbuj Elido

Wklej URL, otrzymaj krótki link

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

Bez rejestracji. Załóż darmowe konto i korzystaj z 50 linków miesięcznie.

Wypróbuj Elido

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

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

Czytaj dalej