Dwa zespoły budujące integracje na tym samym API skracacza URL często trafiają na zupełnie różne architektury. Jeden chce mieć endpoint webhookowy reagujący na każde kliknięcie w czasie rzeczywistym. Drugi pisze cron job odpytujący API analityczne co pięć minut. Oba podejścia są słuszne, ale tylko jedno działa obecnie dla kliknięć w Elido: webhooki dla poszczególnych kliknięć są planowane, a nie wdrożone, więc bieżącą ścieżką dla danych o kliknięciach jest polling. Wybór między nimi ma realne konsekwencje dla opóźnień, kosztów operacyjnych i tego, jak Twój system degraduje się przy awariach.
Ten artykuł przedstawia rzeczywiste kompromisy.
Dwa wzorce
Polling
Polling oznacza, że Twój kod cyklicznie pyta API o ostatnie dane kliknięć. Cron job się budzi, wywołuje /v1/workspaces/{workspace_id}/analytics/clicks/recent lub /v1/workspaces/{workspace_id}/analytics/summary, przetwarza wyniki, a następnie zasypia do następnego interwału.
Przepływ danych jest oparty na zasadzie pull: Twoja infrastruktura inicjuje każdą interakcję. Serwer API nie ma wiedzy o Twoich wewnętrznych systemach - po prostu odpowiada na wysyłane zapytania.
Webhooks
Webhooks oznaczają, że serwer Elido wysyła zdarzenie na Twój endpoint HTTPS. Twój receiver obsługuje je, zwraca 2xx i dostarczenie jest zapisywane jako udane. Obecnie te zdarzenia obejmują linki i domeny (link.created, link.updated, link.expired, domain.verified i kilka innych). Zdarzenie click.created dla poszczególnych kliknięć jest planowane i nie jest jeszcze emitowane, więc na razie kliknięcie nie uruchamia żadnego webhooka.
Przepływ danych jest oparty na zasadzie push: platforma inicjuje kontakt. Twój endpoint musi być dostępny z internetu, musi posiadać TLS i musi odpowiadać niezawodnie.
Kiedy polling jest właściwym wyborem
Polling sprawdza się w określonym zbiorze warunków. Jeśli większość z poniższych pasuje do Twojej sytuacji, zacznij od pollingu i sięgaj po webhooks dopiero wtedy, gdy konkretny problem Cię do tego zmusi.
Kontrolujesz obie strony integracji. Gdy konsumentem jest dashboard lub narzędzie raportowe, które sam posiadasz i operujesz, polling daje przewidywalne i ograniczone zachowanie. Sam decydujesz o interwale, oknie czasowym i sposobie obsługi częściowych wyników.
Twój przypadek użycia jest retrospektywny. Tygodniowe raporty kampanii, miesięczne zadania agregacji i potoki uzgadniania danych nie zyskują na opóźnieniu poniżej minuty. Cron job uruchamiany co godzinę pod adresem /summary lub /breakdown/country jest architektonicznie prostszy i łatwiejszy w rozumowaniu niż stanowy receiver webhookowy z obsługą ponownych prób.
Nie masz publicznego endpointu do wystawienia. Webhooks wymagają URL dostępnego z infrastruktury Elido. Jeśli Twoja integracja działa w sieci prywatnej, funkcji Lambda bez stabilnego URL lub na lokalnej maszynie dewelopera, skonfigurowanie przychodzącego endpointu HTTPS może kosztować więcej złożonością operacyjną, niż warta jest uzyskana korzyść z opóźnienia.
Wolumen jest niski. Przy kilku tysiącach kliknięć dziennie różnica między czasem rzeczywistym a pięciominutowym opóźnieniem rzadko jest widoczna dla użytkowników końcowych. Polling jest prosty do zrozumienia, prosty w debugowaniu i nie generuje niespodzianek infrastrukturalnych.
Kiedy webhooks są właściwym wyborem
Webhooks mają sens, gdy opóźnienie jest wymaganiem produktowym, a nie miłym dodatkiem. W przypadku danych o kliknięciach przeczytaj tę sekcję jako opis planu na czas, gdy click.created zostanie wdrożone; do tego czasu te same przypadki użycia obsługuje krótki interwał pollingu endpointu clicks/recent.
Budujesz licznik na żywo lub UX w czasie rzeczywistym. Jeśli Twój produkt pokazuje użytkownikom liczbę kliknięć aktualizowaną w widoczny sposób w ciągu sekund od przekierowania, polling przy jakimkolwiek rozsądnym interwale będzie odczuwalnie przestarzały. Handler webhookowy inkrementujący licznik Redis przy zdarzeniach click.created i udostępniający go przez WebSocket lub SSE do frontendu to architektura, która osiągnie ten cel bez bombardowania API analitycznego, gdy to zdarzenie zostanie wdrożone. Obecnie najbliższym odpowiednikiem jest jednominutowy polling endpointu clicks/recent, który zatrzymuje się na ostatnim znanym wierszu.
Wzbogacasz rekordy CRM przy każdym kliknięciu. Powiązanie zdarzenia kliknięcia z rekordem kontaktu - identyfikacja, który konkretny potencjalny klient kliknął link w Twoim outboundowym e-mailu i aktualizacja jego osi czasu w CRM - jest wrażliwe na czas. Zanim polling się dogoni pięć minut później, przedstawiciel handlowy może już zadzwonić. Handler webhookowy uruchamiający aktualizację CRM w ciągu sekund od kliknięcia będzie właściwym narzędziem, gdy zdarzenia dla poszczególnych kliknięć będą dostępne; do tego czasu utrzymuj krótki interwał pollingu.
Prowadzisz workflow oparte na zdarzeniach. Workflow wyzwalane zdarzeniami kliknięć - wysyłanie e-maila z follow-upem po kliknięciu linku, aktualizacja segmentu subskrybenta, dekrementacja stanu magazynowego - są naturalnymi konsumentami webhooków. Planowane zdarzenie click.created ma zawierać wystarczająco danych, aby działać natychmiast, bez dodatkowego zapytania. Workflow wyzwalane zmianami linków, takimi jak link.created lub link.expired, mogą już korzystać z webhooków.
Masz stabilny, publicznie dostępny endpoint HTTPS. To jest warunek wstępny, od którego wszystko inne zależy. Jeśli masz już infrastrukturę produkcyjną przyjmującą przychodzące webhooks od innych dostawców (Stripe, GitHub, Twilio), dodanie Elido do tego samego receivera jest bezproblemowe.
Ukryte koszty webhooków
Webhooks brzmią prosto: serwer wysyła POST, Ty go obsługujesz. Rzeczywista powierzchnia implementacji jest większa.
Weryfikacja podpisu
Elido podpisuje każde dostarczenie webhooka przy użyciu HMAC-SHA256. Format podpisu to v1=HMAC-SHA256(secret, "${unix_timestamp}.${body}"), dostarczany w nagłówku X-Elido-Signature oraz jako alias w X-Webhook-Signature. Znacznik czasu jest wysyłany oddzielnie w X-Webhook-Timestamp.
Musisz zweryfikować ten podpis przed przetworzeniem ładunku; przewodnik po weryfikacji podpisów webhooków obejmuje również Python, Go i rotację sekretów. Receiver pomijający weryfikację przetworzy każdy POST docierający do endpointu, w tym sfałszowane żądania od każdego, kto odkryje URL Twojego webhooka.
Poniżej znajduje się minimalny handler Express w TypeScript, który weryfikuje podpis przed wykonaniem czegokolwiek z ładunkiem:
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 === "link.updated") {
// Handle the link event
console.log("link updated:", event.data);
}
// Always return 2xx promptly - do heavy processing async
return res.status(200).json({ received: true });
});
Okno powtórek
Sprawdzenie znacznika czasu w powyższym przykładzie wymusza to, co dokumentacja Elido nazywa oknem powtórek. Bez niego atakujący, który przechwyci jeden ważny podpisany ładunek, może go odtwarzać w nieskończoność - podpis pozostaje ważny na zawsze, ponieważ jest obliczany na podstawie stałego znacznika czasu. Przy tym sprawdzeniu ładunek starszy niż pięć minut jest odrzucany niezależnie od tego, czy podpis jest ważny.
Ustaw tolerancję na coś, co Twoja infrastruktura może obsłużyć. Pięć minut to konwencjonalne domyślne ustawienie, zgodne z tym, co używa Stripe. Jeśli Twój receiver okazjonalnie schodzi offline na kilka minut podczas wdrożeń, to okno daje mu czas na powrót i nadal przetwarza dostarczenia w locie.
Ponowne próby i idempotentność
Elido ponawia nieudane dostarczenia według harmonogramu backoff: pierwsza ponowna próba następuje 5 minut po niepowodzeniu, a druga 15 minut później. Dostarczenie ma domyślnie łącznie 3 próby. Po 3 nieudanych próbach dostarczenie jest oznaczane jako trwale nieudane i pojawia się w rejestrze dostarczeń endpointu.
Oznacza to, że Twój receiver może otrzymać to samo zdarzenie więcej niż raz. Każde przetwarzanie mające efekty uboczne - zapis do bazy danych, wysłanie e-maila, aktualizacja licznika - musi być idempotentne. Nagłówek X-Webhook-Delivery zawiera stabilne ID dostarczenia, którego możesz użyć jako klucza idempotentności.
// 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);
Twój endpoint musi być wysoko dostępny
Okno ponownych prób jest skończone. Jeśli Twój receiver jest niedostępny przez ponad około 20 minut (5 + 15), dostarczenia wyczerpią swoje próby i trwale się nie powiodą. W przypadku zdarzeń, gdzie gwarantowane dostarczenie ma znaczenie - wzbogacanie CRM, hooki rozliczeniowe - Twoja infrastruktura receivera wymaga właściwej dostępności, a nie serwera hobbysty, który okazjonalnie się restartuje.
To jest najbardziej niedoceniany koszt webhooków dla zespołów nowych w obszarze przychodzącego HTTP. Polling degraduje się w sposób łagodny: jeśli zadanie pollingu się nie powiedzie, po prostu uruchomi się ponownie przy następnym interwale i dogoni zaległości. Receiver webhookowy, który jest niedostępny, trwale traci zdarzenia, chyba że masz strategię uzgadniania.
Ukryte koszty pollingu
Polling wygląda prosto z zewnątrz. Rzeczywiste koszty kumulują się w produkcji.
Opóźnienie jest głównym ograniczeniem. Cron job uruchamiany co pięć minut oznacza, że dane kliknięć są nieaktualne do pięciu minut. Dla większości retrospektywnych przypadków użycia jest to akceptowalne; dla czegokolwiek skierowanego do użytkownika - nie. Skracanie interwału pomaga, ale nie eliminuje opóźnienia, a bardzo krótkie interwały (poniżej minuty) zaczynają wyglądać bardziej jak bombardowanie API niż polling.
Zmarnowane żądania. Większość interwałów pollingu zwraca te same dane co poprzednie żądanie. Jeśli odpytujesz link o małym ruchu co minutę, a kliknięcia pojawiają się mniej więcej raz na godzinę, 59 na 60 żądań nie zwraca nic nowego. Te żądania nadal wliczają się do Twojego limitu szybkości API.
Limity szybkości. API Elido egzekwuje limity szybkości per-workspace dostosowane do poziomu rozliczeniowego. Zadanie pollingu uruchamiane często na wielu linkach w dużym workspace może trafić na te limity, szczególnie jeśli inna automatyzacja w tym samym workspace też wykonuje wywołania API. API zwraca 429 Too Many Requests z nagłówkiem X-RateLimit-Scope: workspace, gdy tak się dzieje.
Paginacja i pominięte zdarzenia. Endpoint /clicks/recent używa paginacji opartej na kursorze. Jeśli odpytujesz na podstawie stałego okna czasowego - ?from=<last_poll>&to=<now> - a wolumen w tym oknie przekracza rozmiar strony, pominiesz zdarzenia, chyba że będziesz podążać za next_cursor przez wszystkie strony. Implementacja pollingu, która nie obsługuje paginacji, będzie po cichu gubić dane pod obciążeniem.
Wzorzec hybrydowy
W przypadku większości wdrożeń produkcyjnych najlepsza odpowiedź nie jest ani/ani.
Gdy click.created zostanie wdrożone, używaj webhooków jako głównej ścieżki dla reakcji w czasie rzeczywistym: aktualizacje CRM, liczniki na żywo, workflow oparte na zdarzeniach. Opóźnienie jest niskie; narzut operacyjny jest do opanowania, jeśli masz już infrastrukturę przychodzącego HTTPS. Do tego czasu szybką ścieżką jest krótki interwał pollingu endpointu clicks/recent, a opisana poniżej ścieżka uzgadniania działa tak samo w oparciu o niego.
Używaj pollingu jako tygodniowego lub dziennego przebiegu uzgadniania: pobierz pełną serię czasową za poprzedni tydzień, porównaj sumy z tym, co zarejestrował Twój handler webhookowy, i zidentyfikuj wszelkie luki. To wychwytuje dostarczenia, które wyczerpały okno ponownych prób podczas awarii, zdarzenia, które pojawiły się nie po kolei, i wszelkie rozbieżności między Twoim lokalnym stanem a źródłem prawdy Elido.
API analityczne dobrze nadaje się do tej roli. Endpoint /summary zwraca zagregowane sumy dla zakresu dat w jednym zapytaniu; endpoint /timeseries zwraca dzienne zasobniki. Zadanie uzgadniania uruchamiane raz na noc i porównujące zarejestrowane liczniki kliknięć Twojego CRM z podsumowaniem API dla tego samego okna może wykrywać problemy z integralnością danych, zanim staną się problemami widocznymi dla klientów.
Cron pollingu w Pythonie
Polling jest obecnie punktem wyjścia dla każdej integracji kliknięć i będzie działać także po wdrożeniu webhooków dla poszczególnych kliknięć. Poniżej znajduje się minimalna implementacja wykorzystująca bibliotekę schedule, która wywołuje clicks/recent co pięć minut (szczegółowe omówienie parametrów kursora, daty i strefy czasowej znajduje się w przewodniku po API analitycznym linków):
import schedule
import time
import requests
import os
API_BASE = "https://api.elido.app/v1"
WORKSPACE_ID = os.environ["ELIDO_WORKSPACE_ID"]
API_KEY = os.environ["ELIDO_API_KEY"]
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Newest click already processed, kept across runs. clicks/recent returns
# newest first and next_cursor pages to older rows, so each run reads from
# the top and stops when it reaches this row.
_last_seen = None
def poll_recent_clicks():
global _last_seen
params = {"limit": 100}
newest = None
done = False
while not done:
resp = requests.get(
f"{API_BASE}/workspaces/{WORKSPACE_ID}/analytics/clicks/recent",
headers=HEADERS,
params=params,
timeout=10,
)
resp.raise_for_status()
body = resp.json()
for click in body.get("items", []):
key = (click["ts"], click["link_id"])
if key == _last_seen:
done = True
break
if newest is None:
newest = key
process_click(click)
next_cursor = body.get("next_cursor")
if not next_cursor:
break
params["cursor"] = next_cursor
if newest:
_last_seen = newest
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)
W wdrożeniu produkcyjnym zastąp print swoim rzeczywistym ujściem - zapisem do bazy danych, wywołaniem API CRM, publikacją do kolejki wiadomości - i dodaj obsługę błędów z wykładniczym backoffem wokół wywołania requests.get.
Filtrowanie botów i co to oznacza dla Twojej integracji
Jeden szczegół, który dotyczy obu wzorców: warstwa przekierowań Elido filtruje kliknięcia botów, zanim zostaną zapisane. Żądania od Googlebot, Bingbot, Slackbot, monitorów czasu pracy, curl, bibliotek skryptowych i pustych User-Agentów nie są liczone jako kliknięcia, nie pojawiają się w wynikach API analitycznego i nie będą generować zdarzeń click.created, gdy te zostaną wdrożone.
Ma to znaczenie, ponieważ oznacza, że Twój handler webhookowy lub zadanie pollingu pracuje z liczbą ludzkich przekierowań, a nie surową liczbą żądań HTTP. Jeśli korelujesz dane kliknięć Elido z metrykami po stronie serwera - logami serwera aplikacji, logami dostępu CDN - oczekuj, że liczby Elido będą niższe. Rozbieżność to nie błąd; to filtr botów usuwający szum zanim dotrze do Ciebie.
Aby uzyskać więcej szczegółów na temat tego, co obejmuje filtr botów i jak skorer podejrzeń oznacza graniczny ruch, przewodnik po analityce zawiera pełny opis. Aby zapoznać się z właściwościami bezpieczeństwa schematu podpisywania webhooków - w tym formatem HMAC, powiązaniem ze znacznikiem czasu i tym, czemu zapobiega - odwołaj się do listy kontrolnej bezpieczeństwa.
Strona cennika zawiera zestawienie tego, które poziomy planów obejmują endpointy webhookowe i przy jakich limitach wolumenu dostarcze.
Powiązane wpisy na blogu
- Dlaczego używamy ClickHouse do analityki kliknięć (a nie Postgres)
- Śledzenie po stronie serwera GA4 przez warstwę przekierowania
- Osiąganie p95 < 15ms dla przekierowań z FRA, ASH i SGP
- Atrybucja bez plików cookie: co nadal działa w 2026 roku
- Weryfikacja podpisów webhooków: HMAC-SHA256 w Node, Pythonie i Go
- API analityczne linków: pobieranie statystyk kliknięć za pomocą klucza API
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