Дві команди, що будують на основі одного й того ж API для скорочення URL-адрес, часто приходять до абсолютно різних архітектур інтеграції. Одна команда налаштовує ендпоінт вебхуку і реагує на кожен клік у режимі реального часу. Інша пише cron-завдання, яке опитує (поллінг) API аналітики кожні п'ять хвилин. Обидва варіанти є валідними. Рішення між ними має реальні наслідки для затримки (latency), операційних витрат і того, наскільки ваша система деградує, коли щось йде не так.
Цей допис описує реальні компроміси.
Два паттерни#
Поллінг (опитування)#
Поллінг означає, що ваш код запитує в API дані про останні кліки за розкладом. Cron-завдання прокидається, викликає /v1/analytics/workspaces/{id}/clicks/recent або /v1/analytics/workspaces/{id}/summary, обробляє результати, а потім засинає до наступного інтервалу.
Потік даних базується на витягуванні (pull-based): ваша інфраструктура ініціює кожну взаємодію. API-сервер нічого не знає про ваші внутрішні системи - він просто відповідає на надіслані вами запити.
Вебхуки#
Вебхуки означають, що сервер Elido надсилає подію click.recorded на ваш HTTPS-ендпоінт невдовзі після обробки кліку. Ваш приймач обробляє її, повертає 2xx, і доставка фіксується як успішна.
Потік даних базується на проштовхуванні (push-based): платформа ініціює контакт. Ваш ендпоінт має бути доступним з інтернету, потребує TLS і повинен надійно відповідати.
Коли поллінг - це правильний вибір#
Поллінг підходить для певного набору умов. Якщо більшість із наведеного нижче стосується вашої ситуації, почніть із поллінгу і переходьте до вебхуків лише тоді, коли конкретна проблема змусить вас це зробити.
Ви контролюєте обидві сторони інтеграції. Коли споживачем є дашборд або інструмент звітності, яким ви володієте та керуєте, поллінг дає вам передбачувану, обмежену поведінку. Ви вирішуєте інтервал; ви вирішуєте часове вікно; ви вирішуєте, як обробляти часткові результати.
Ваш випадок використання є ретроспективним. Щотижневі звіти про кампанії, щомісячні завдання з агрегації та процеси звірки не виграють від субхвилинної затримки. Cron-завдання, що запускається щогодини для /summary або /breakdown/country, архітектурно простіше та легше для розуміння, ніж станний (stateful) приймач вебхуків з обробкою повторних спроб.
У вас немає публічного ендпоінту для експонування. Вебхуки потребують URL-адресу, доступну з інфраструктури Elido. Якщо ваша інтеграція працює всередині приватної мережі, у функції Lambda без стабільної URL-адреси або на локальній машині розробника, налаштування вхідного HTTPS-ендпоінту може коштувати дорожче з точки зору операційної складності, ніж вигода від зменшення затримки.
Обсяг невеликий. При кількох тисячах кліків на день різниця між режимом реального часу та п'ятихвилинною затримкою рідко помітна кінцевим користувачам. Поллінг легко зрозуміти, легко налагоджувати, і він не створює сюрпризів в інфраструктурі.
Коли вебхуки - це правильний вибір#
Вебхуки мають сенс, коли низька затримка є вимогою продукту, а не просто побажанням.
Ви створюєте живий лічильник або UX у реальному часі. Якщо ваш продукт показує користувачам кількість кліків, яка візуально оновлюється протягом декількох секунд після редиректу, поллінг з будь-яким розумним інтервалом буде здаватися помітно застарілим. Обробник вебхуків, який інкрементує лічильник у Redis для подій click.recorded і відображає його через з'єднання WebSocket або SSE на фронтенді - це архітектура, яка досягає цього без перевантаження API аналітики.
Ви збагачуєте записи в CRM для кожного кліку. Прив'язка події кліку до запису контакту - ідентифікація того, за яким саме посиланням перейшов потенційний клієнт у вашому листі, та оновлення його таймлайну в CRM - критична до часу. До того моменту, як завдання поллінгу наздожене дані через п'ять хвилин, менеджер з продажів уже може зателефонувати клієнту. Обробник вебхуків, який запускає оновлення CRM протягом декількох секунд після кліку, є правильним інструментом.
Ви запускаєте робочі процеси, керовані подіями (event-driven). Робочі процеси, що запускаються подіями кліку - надсилання наступного листа після кліку по посиланню, оновлення сегмента підписника, зменшення кількості товару на складі - є природними споживачами вебхуків. Подія click.recorded несе достатньо даних, щоб діяти негайно, без додаткового запиту.
У вас є стабільний, публічно доступний HTTPS-ендпоінт. Це передумова, від якої залежить усе інше. Якщо у вас уже є продакшн-інфраструктура, яка приймає вхідні вебхуки від інших провайдерів (Stripe, GitHub, Twilio), додати Elido до того самого приймача буде нескладно.
Приховані витрати вебхуків#
Вебхуки звучать просто: сервер надсилає POST, ви його обробляєте. Реальна поверхня впровадження більша.
Верифікація підпису#
Elido підписує кожну доставку вебхуку за допомогою HMAC-SHA256. Формат підпису - v1=HMAC-SHA256(secret, "${unix_timestamp}.${body}"), що передається в заголовку X-Webhook-Signature. Мітка часу (timestamp) надсилається окремо в X-Webhook-Timestamp. Обидва генеруються в services/webhook-dispatcher/internal/signing/hmac.go.
Ви повинні перевірити цей підпис перед обробкою корисного навантаження. Приймач, який пропускає верифікацію, оброблятиме будь-який POST-запит, що надійде на ендпоінт, включаючи підроблені запити від будь-кого, хто дізнається URL-адресу вашого вебхуку.
Ось мінімальний обробник Express на TypeScript, який перевіряє підпис перед будь-якими діями з корисним навантаженням:
import express, { Request, Response } from "express";
import crypto from "crypto";
const app = express();
// Використовуйте raw body middleware - JSON-парсери споживають потік перед тим, як ви зможете його хешувати
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");
// Використовуйте timingSafeEqual для запобігання атак на основі часу
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" });
}
// Відхиляти запити, старіші за 5 хвилин
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") {
// Обробити подію кліку
console.log("click recorded:", event.data);
}
// Завжди негайно повертайте 2xx - виконуйте важку обробку асинхронно
return res.status(200).json({ received: true });
});
Вікно повтору (replay window)#
Перевірка мітки часу в наведеному вище прикладі забезпечує те, що в документації Elido називається вікном повтору. Без неї зловмисник, який перехопив одне валідне підписане корисне навантаження, може повторювати його нескінченно - підпис залишається дійсним назавжди, оскільки він обчислюється на основі фіксованої мітки часу. З цією перевіркою навантаження, старіше за п'ять хвилин, відхиляється незалежно від того, чи є підпис валідним.
Встановіть допуск, який може витримати ваша інфраструктура. П'ять хвилин - це стандартне значення, яке також використовує Stripe. Якщо ваш приймач іноді виходить з ладу на кілька хвилин під час розгортання, це вікно дає йому час повернутися в стрій і все одно обробити доставки, що перебували в дорозі.
Повторні спроби та ідемпотентність#
webhook-dispatcher в Elido повторює невдалі доставки за розкладом затримки: перша спроба через 1 хвилину, друга через 5 хвилин, третя через 15 хвилин. Максимальна кількість спроб на одну доставку за замовчуванням дорівнює 3, як визначено в схемі webhook_deliveries. Після 3 невдалих спроб доставка позначається як остаточно невдала і з'являється в дашборді сповіщень.
Це означає, що ваш приймач може отримати ту саму подію більше одного разу. Будь-яка обробка, що має побічні ефекти - запис у базу даних, надсилання електронного листа, оновлення лічильника - повинна бути ідемпотентною. Заголовок X-Webhook-Delivery містить стабільний ID доставки, який ви можете використовувати як ключ ідемпотентності.
// Перед обробкою перевірте, чи ця доставка вже була оброблена
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 });
}
// Позначити як оброблену з TTL, що охоплює вікно повторних спроб
await redis.set(`webhook:delivery:${deliveryId}`, "1", "EX", 3600);
Ваш ендпоінт повинен бути високонадійним#
Вікно повторних спроб обмежене. Якщо ваш приймач не працює більше ніж приблизно 21 хвилину (1 + 5 + 15), спроби доставити дані вичерпаються, і доставка буде назавжди позначена як невдала. Для подій, де гарантована доставка має значення - збагачення CRM, білінгові хуки - інфраструктура вашого приймача потребує належної доступності, а не аматорського сервера, який періодично перезавантажується.
Це витрата вебхуків, яку команди, нові у роботі з вхідними HTTP-запитами, недооцінюють найчастіше. Поллінг деградує витончено: якщо завдання поллінгу не вдалося, воно просто запуститься знову в наступному інтервалі та наздожене дані. Приймач вебхуків, який недоступний, втрачає події назавжди, якщо у вас немає стратегії звірки.
Приховані витрати поллінгу#
Поллінг виглядає просто ззовні. Реальні витрати накопичуються в продакшені.
Затримка є визначальним обмеженням. Cron-завдання, що запускається кожні п'ять хвилин, означає, що дані про кліки застаріли до п'яти хвилин. Для більшості ретроспективних випадків використання це прийнятно; для будь-чого, що бачить користувач - ні. Скорочення інтервалу допомагає, але не усуває затримку, а дуже короткі інтервали (менше хвилини) починають нагадувати "бомбардування" API, а не поллінг.
Марні запити. Більшість інтервалів поллінгу повертають ті самі дані, що й попередній запит. Якщо ви опитуєте посилання з низьким трафіком щохвилини, а кліки надходять приблизно один раз на годину, 59 з кожних 60 запитів не принесуть нічого нового. Ці запити все одно враховуються у вашому ліміті запитів до API (rate limit).
Ліміти запитів (Rate limits). API Elido застосовує ліміти запитів для кожного робочого простору залежно від тарифного плану. Завдання поллінгу, яке часто запускається для багатьох посилань у великому робочому просторі, може досягти цих лімітів, особливо якщо інша автоматизація в тому самому робочому просторі також робить виклики до API. У такому разі API повертає 429 Too Many Requests із заголовком X-RateLimit-Scope: workspace.
Пагінація та пропущені події. Ендпоінт /clicks/recent використовує пагінацію на основі курсора. Якщо ви робите поллінг у фіксованому часовому вікні - ?from=<last_poll>&to=<now> - і обсяг даних у цьому вікні перевищує розмір сторінки, ви пропустите події, якщо не пройдете через усі сторінки за допомогою next_cursor. Реалізація поллінгу, яка не обробляє пагінацію, буде мовчки втрачати дані під навантаженням.
Гібридний паттерн#
Для більшості випадків використання в продакшені найкращою відповіддю не є вибір "або-або".
Використовуйте вебхуки як основний шлях для реакції в реальному часі: оновлення CRM, живі лічильники, робочі процеси, керовані подіями. Затримка низька; операційні витрати прийнятні, якщо у вас вже є інфраструктура для вхідних HTTPS-запитів.
Використовуйте поллінг як щотижневий або щоденний етап звірки: витягуйте повний часовий ряд за попередній тиждень, порівнюйте підсумки з тим, що зафіксував ваш обробник вебхуків, і ідентифікуйте будь-які прогалини. Це дозволяє виявити доставки, які вичерпали вікно повторних спроб під час збою, події, що надійшли не по порядку, та будь-які розбіжності між вашим локальним станом і джерелом істини Elido.
API аналітики добре підходить для цієї ролі. Ендпоінт /summary повертає агреговані підсумки для діапазону дат в одному запиті; ендпоінт /timeseries повертає щоденні сегменти. Завдання звірки, яке запускається один раз на ніч і порівнює кількість кліків, зафіксовану у вашій CRM, з підсумками API за той самий період, може виявити проблеми з цілісністю даних до того, як вони стануть проблемою для клієнтів.
Cron для поллінгу на Python#
Для команд, які хочуть почати з поллінгу і пізніше перейти до вебхуків, ось мінімальна реалізація з використанням бібліотеки schedule, яка викликає /clicks/recent з п'ятихвилинним інтервалом:
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}"}
# Відстежуйте курсор між інтервалами поллінгу, щоб отримувати лише нові кліки
_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:
# Зберегти поточний курсор для наступного запуску
if items:
_cursor = None # скидання: наступний поллінг починається з поточного моменту
break
params["cursor"] = next_cursor
def process_click(click: dict):
# Замініть на вашу реальну логіку обробки
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() # запустити один раз при старті, щоб наздогнати дані
while True:
schedule.run_pending()
time.sleep(10)
У продакшн-розгортанні замініть print на ваш реальний приймач даних - запис у базу даних, виклик API CRM, публікацію в черзі повідомлень - і додайте обробку помилок з експоненціальною затримкою навколо виклику requests.get.
Фільтрація ботів та її значення для вашої інтеграції#
Одна деталь, яка впливає на обидва паттерни: сервіс edge-redirect в Elido фільтрує кліки ботів перед тим, як надсилати події кліків у конвеєр обробки. Запити від Googlebot, Bingbot, Slackbot, моніторів доступності, curl, скриптових бібліотек та порожніх User-Agents не створюють подій click.recorded і не з'являються в результатах API аналітики.
Це важливо, тому що це означає, що ваш обробник вебхуків або завдання поллінгу працює з кількістю кліків від людей, а не з необробленою кількістю HTTP-запитів. Якщо ви корелюєте дані кліків Elido з метриками на стороні сервера - логами вашого додатка, логами доступу CDN - очікуйте, що числа Elido будуть нижчими. Ця розбіжність не є помилкою; це фільтр ботів, який видаляє шум до того, як він дійде до вас.
Більш детальну інформацію про те, що охоплює фільтр ботів і як скорер підозрілості позначає граничний трафік, можна знайти в посібнику з аналітики. Про властивості безпеки схеми підпису вебхуків - включаючи формат HMAC, прив'язку мітки часу та те, чому вона запобігає - дивіться в чек-листі безпеки.
На сторінці цін є розбивка того, які тарифні плани включають ендпоінти вебхуків і з якими обмеженнями на обсяг доставки.
Схоже в блозі#
Спробуйте Elido
Вставте URL - отримайте коротке посилання
Без реєстрації. Посилання живе 30 днів. Зареєструйтесь, щоб зберегти назавжди.
Безкоштовно, без реєстрації · 2 на день