8 хв читанняІнтеграції

Вебхуки чи опитування: вебхуки для трекінгу кліків

Вебхуки чи опитування для трекінгу кліків: затримка й кількість запитів, повтори, ідемпотентність і підписи, таблиця вибору та код Node.

Sasha Ehrlich
Compliance · EU residency

Використовуйте вебхуки, коли людина або робочий процес має відреагувати за лічені секунди, а опитування - коли кількох хвилин затримки цілком досить і ви не хочете тримати публічну кінцеву точку. В Elido вибір має підступ: вебхуків для трекінгу кліків ще не існує. Вебхуки охоплюють події посилань, доменів, учасників і робочого простору, а подія click.created для кожного кліку запланована, але не видається. Сьогодні дані про кліки беруть опитуванням API аналітики.

Ця стаття рахує затримку й обсяг запитів, описує, що гарантує кожен шаблон (повторні спроби, дублікати, порядок, підписи), і завершується таблицею вибору. Код запускали в Node 22 проти локальних моків, перш ніж додати сюди. Повний каталог подій посилань дивіться в матеріалі вебхуки для подій посилань; про кінцеві точки кліків - у посібнику з API аналітики посилань.

Вебхуки й опитування: по одному абзацу

Опитування означає, що ваш код запитує нові дані за розкладом. Завдання cron викликає GET /v1/workspaces/{workspace_id}/analytics/clicks/recent, обробляє нове й засинає. Кожен запит ініціюєте ви. Публічний URL не потрібен.

Вебхуки змінюють напрямок. Elido надсилає підписаний POST із JSON-конвертом на вашу HTTPS-кінцеву точку, коли спрацьовує подія, а відповідь 2xx позначає доставку виконаною. Потрібна досяжна кінцева точка. TLS. Обробник, що переживає дублікати. Сьогодні події такі: link.created, link.updated, link.expired, domain.verified та решта списку в документації вебхуків.

Діаграма поруч: за опитування ваше завдання cron надсилає GET до API Elido й забирає рядки кожні п'ять хвилин; за вебхуків сервер Elido надсилає POST на вашу HTTPS-кінцеву точку, яка повертає 2xx, а подія click.created для кожного кліку ще запланована. Сторінка 'Вебхуки' в Elido зі списком ендпоінта з підписаними подіями посилань, активним статусом і датою створення

Арифметика затримки й вартості

Затримка опитування визначається інтервалом. З інтервалом I хвилин подія чекає в середньому I/2 хвилин, а в гіршому разі I хвилин, плюс будь-яка затримка, з якою дані стають доступними для запиту. Запитів на добу 1 440 / I.

  • 1 хвилина: 1 440 запитів на добу, 30 секунд середньої затримки, 1 хвилина в гіршому разі.
  • 5 хвилин: 288 запитів, 2,5 хвилини в середньому, 5 хвилин у гіршому разі.
  • 15 хвилин: 96 запитів, 7,5 хвилини в середньому, 15 хвилин у гіршому разі.
  • 60 хвилин: 24 запити, 30 хвилин у середньому, 60 хвилин у гіршому разі.

Вартість проявляється як марні запити, а не як тиск на ліміти. Опублікований ліміт робочого простору на Free - 10 запитів на секунду, тобто 864 000 на добу (дивіться ліміти API та ідемпотентність). Один опитувач з інтервалом 1 хвилина використовує 1 440 / 864 000 = 0,17% цього. Ліміти ви зачепите, коли опитуєте кожне посилання окремо серед сотень посилань або коли робочий простір ділять інші автоматизації, а не з одним потоком.

Марнотратність - ось що важливо. Скажімо, робочий простір змінює 40 посилань на добу, а ви стежите за цим опитуванням щохвилини. Щонайбільше 40 із 1 440 запитів повертають щось нове, тож принаймні 1 400 (97%) порожні. Одержувач вебхуків отримує 40 запитів на ті самі 40 подій. У гіршому разі, з трьома спробами кожна, це 120.

Вебхуки виграють за затримкою, яка зазвичай становить секунди, бо воркер доставки безперервно підхоплює рядки в очікуванні. Вони програють у тому, що опитування отримує безкоштовно: пропущений запуск виправляє наступний, бо дані досі лежать в API. Пропущений вебхук виправляється, лише якщо ви збудували щось для виправлення.

Гарантії доставки: що обіцяє Elido

Ця поведінка випливає з коду доставки та довідкової документації, перевірено 1 жовтня 2026 року.

Повторні спроби. Доставка отримує три спроби: одну одразу, одну через 5 хвилин після збою, одну через 15 хвилин після цього, приблизно 20 хвилин від початку до кінця. Будь-який статус не 2xx, мережева помилка або відповідь повільніша за 10 секунд - це збій. Після третьої доставку позначають як невдалу; Retry у дашборді або POST .../deliveries/{delivery_id}/retry знову вмикає її зі свіжим бюджетом із трьох спроб. Для порівняння, Stripe повторює події в бойовому режимі до трьох діб. Двадцять хвилин - це мало. Не розраховуйте, що відмову одержувача буде покрито.

Ідемпотентність. Доставка відбувається принаймні один раз. Заголовок X-Webhook-Delivery ідентифікує одну доставку на одну кінцеву точку й не змінюється між автоматичними чи ручними повторами, тож це ваш ключ дедуплікації.

Порядок. Гарантії порядку немає. Повтор ранньої події може надійти після пізнішої, тож перш ніж щось перезаписувати, порівняйте data.updated_at зі збереженим.

Підписи. Кожна підписана доставка несе X-Webhook-Signature (псевдонім X-Elido-Signature) зі значенням v1= плюс шістнадцятковий HMAC-SHA256 від {timestamp}.{raw_body}, а мітка часу - в X-Webhook-Timestamp. Мітка часу свіжа в кожній спробі, тож законний повтор ніколи не застаріє. Під час ротації секрету X-Elido-Signature-Previous несе підпис зі старого секрету протягом семи днів. Перевірка підписів вебхуків охоплює Python, Go і налагодження розбіжності.

Транспорт і пауза. Нові кінцеві точки подій мають використовувати https; звичайний http приймається лише для localhost і 127.0.0.1. Значення is_active false ставить кінцеву точку на паузу. Після виправлення від 22 вересня це зупиняє й доставки в черзі.

Одержувач вебхуків у Node

Цей одержувач перевіряє підпис за сирими байтами, застосовує п'ятихвилинне вікно, приймає заголовок попереднього секрету під час ротації та дедуплікує за ID доставки. П'ятихвилинний допуск - ваш вибір, а не Elido, і він збігається зі стандартним значенням Stripe. Я запустив його проти локального підписувача: коректний запит повернув 200, повтор із тим самим ID доставки повернув "duplicate", хибний секрет і мітка часу 400-секундної давності обидва повернули 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);

Перевірка довжини перед timingSafeEqual важлива. Node кидає виняток для буферів різного розміру. Спершу відповідайте, потім працюйте. 10-секундна межа - це місце, де повільний, але успішний обробник стає дублікатом.

Опитувач кліків у Node

Доки click.created не вийде, це шлях для кліків. clicks/recent повертає найновіші першими, приймає limit до 500 (типово 100) і повертає next_cursor, щойно сторінка заповнена. Кожен запуск читає з початку й зупиняється, коли досягає найновішого вже обробленого кліку. Я запустив його проти моку, що віддає по два рядки на сторінку: перший запуск обробив 5 кліків, другий - лише 2 нові, третій - жодного.

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

Два обмеження, названі прямо. Курсор упорядковує за міткою часу й ID посилання, тож два кліки по одному посиланню в ту саму секунду мають спільний ключ; якщо не можна дозволити собі рідкісний пропущений чи повторений клік, зробіть handle ідемпотентним за повним рядком. А lastSeen живе в пам'яті, тож зберігайте його між перезапусками, інакше перезапуск програє недавню історію наново.

Не готові будувати жодне з двох? Це нормально. Відкрийте дашборд і спершу скористайтеся аналітикою дашборда, а API додайте, коли зрозумієте, які числа потрібні за розкладом.

Таблиця вибору

СитуаціяЩо використати сьогодніЧому
Реагувати на зміни посилань чи доменів за секундиВебхук на link.*, domain.*Події надходять самі; подія кліку не потрібна
Живий лічильник кліків у вашому продуктіОпитування clicks/recent щохвилиниВебхука кліків ще немає; 1 440 запитів на добу - значно в межах ліміту
Збагачення CRM на кожен клікФорвардер подій або часте опитуванняСередня затримка опитування 30 секунд зазвичай прийнятна
Тижневі чи місячні звітиОпитуйте summary і timeseriesРетроспективні дані нічого не виграють від push
Немає публічної кінцевої точкиОпитуванняВебхукам потрібен https URL, досяжний з інтернету
Не можна втратити подію посиланняВебхук плюс нічна звіркаПовтори покривають близько 20 хвилин, опитування - решту

Коли вийде потік вибіркових click.created, рядки живого лічильника й CRM перейдуть до колонки вебхуків, а опитування стане проходом звірки. До того форвардери подій, як-от події кліків у Mixpanel, - варіант push-стилю для даних кліків.

Гібридний шаблон

Для всього, що не можна втратити, запускайте обидва. Вебхук - швидкий шлях. Опитування за розкладом порівнює підсумки й знаходить прогалини: доставки, що вичерпали три спроби під час відмови, або події, що надійшли не за порядком.

Звіти summary і timeseries повертають підсумки та денні відрізки за діапазон дат в одному виклику, що робить нічне порівняння дешевим. Пам'ятайте, що фільтр ботів прибирає краулери й монітори до запису кліків, тож ваші підсумки не збігатимуться із сирими логами сервера; що виключається, пояснює посібник з аналітики. Решту охоплює чекліст безпеки. Якщо проєктуєте власні вебхуки, корисним довідником є специфікація Standard Webhooks.

Сторінка документації API в дашборді Elido з посиланнями на довідник API, SDK, специфікацію OpenAPI та керування ключами API

Екран документації API у DEMO-просторі з прикладними даними; це не акаунт клієнта.

Передзапусковий чекліст для будь-якого шаблону

Перш ніж випускати одержувача чи опитувача, пройдіть шість перевірок. Більшість інцидентів у продакшені з обома шаблонами виникає через пропуск однієї з них.

  • Вебхук: відповідайте 2xx протягом 10 секунд, а повільну роботу виконуйте після відповіді.
  • Вебхук: перевіряйте підпис за сирим тілом до розбору JSON.
  • Вебхук: зберігайте X-Webhook-Delivery з унікальним індексом, щоб повтор не міг порахуватися двічі.
  • Опитувач: зберігайте курсор або ключ останнього побаченого поза пам'яттю процесу.
  • Опитувач: відступайте при 429 чи 5xx замість повтору зі звичайним інтервалом.
  • Обидва: сповіщайте, коли жодної події чи нового кліку не надходило довше за ваш звичайний тихий період, бо тиша - це те, як виглядає зламана інтеграція. Незалежні огляди, як-от посібник Hookdeck про вебхуки й опитування API, доходять тієї самої поради: push для швидкості, опитування для звірки.

Читайте також у блозі

Поширені запитання

Яка різниця між вебхуками й опитуванням?

За опитування ваш код запитує в API нові дані за розкладом. За вебхуків сервер надсилає HTTP POST на вашу кінцеву точку, коли щось відбувається. Опитування коштує запитів і додає до одного інтервалу затримки. Вебхуки коштують постійно ввімкненої публічної кінцевої точки та роботи з повторними спробами й дублікатами.

Чи має Elido вебхуки для трекінгу кліків?

Ще ні. Вебхуки охоплюють події посилань, доменів, учасників і робочого простору, як-от link.created і link.expired. Подія click.created запланована, але зараз її ніхто не публікує. Для даних на рівні кліку опитуйте звіт clicks/recent в API аналітики з його курсором або скористайтеся форвардером подій.

Як часто опитувати API аналітики?

Підберіть інтервал під рішення, якого стосуються дані. Одна хвилина підходить для майже живого лічильника, від п'яти до п'ятнадцяти хвилин - для дашбордів, щогодини або щодня - для звітів. Опитування раз на хвилину дає 1 440 запитів на добу, раз на 5 хвилин - 288, тож коротший інтервал рідко виправданий.

Чи гарантовано вебхуки надходять рівно один раз?

Ні. Доставка вебхуків відбувається принаймні один раз, тож одержувач бачить дублікати після тайм-ауту чи ручного повторного надсилання. Elido зберігає те саме значення X-Webhook-Delivery між повторними спробами, тож зберігайте цей ID і пропускайте все, що вже оброблено.

Що буде, якщо моя кінцева точка вебхука недоступна?

Elido робить три спроби: одну негайно, другу через п'ять хвилин після збою і третю через п'ятнадцять хвилин після цього. Якщо всі три невдалі, доставку позначають як невдалу, доки хтось не натисне Retry або не викличе кінцеву точку повтору. Відмови довші за приблизно двадцять хвилин потребують завдання звірки.

Чи можна поєднати вебхуки й опитування?

Так, і для всього важливого варто. Використовуйте вебхук як швидкий шлях, а опитування за розкладом як страховку, що знаходить події, яких ваш одержувач не отримав. Опитування може йти щогодини або щоночі, бо мусить лише закривати прогалини.

Спробуйте Elido

Вставте URL - отримайте коротке посилання

Без реєстрації. Посилання живе 30 днів. Зареєструйтесь, щоб зберегти назавжди.

Без реєстрації. Зареєструйтеся безкоштовно і отримайте 50 посилань на місяць.

Спробуйте Elido

URL-скорочувач із хостингом у ЄС: власні домени, глибока аналітика, відкритий API. Безкоштовний тариф - без кредитної картки.

Теги
webhooks vs polling
click tracking webhooks
click tracking api
url shortener webhooks
webhook retries idempotency
link analytics api

Читати далі