Две команды, создающие интеграцию на базе одного и того же API сокращателя ссылок, часто приходят к совершенно разным архитектурам. Одна команда хочет эндпоинт для вебхуков, который реагирует на каждый клик в режиме реального времени. Другая пишет задачу cron, которая опрашивает API аналитики каждые пять минут. Оба варианта допустимы, но для кликов в Elido сегодня работает только один: вебхуки для отдельных кликов запланированы, но еще не реализованы, поэтому сейчас рабочий путь получения данных о кликах - поллинг. Выбор между ними имеет реальные последствия для задержки (latency), эксплуатационных расходов и того, насколько ваша система будет деградировать при возникновении проблем.
В этом посте представлены основные компромиссы.
Два паттерна
Поллинг
Поллинг (polling) означает, что ваш код запрашивает у API последние данные о кликах по расписанию. Задача cron просыпается, вызывает /v1/workspaces/{workspace_id}/analytics/clicks/recent или /v1/workspaces/{workspace_id}/analytics/summary, обрабатывает результаты и засыпает до следующего интервала.
Поток данных основан на «вытягивании» (pull-based): ваша инфраструктура инициирует каждое взаимодействие. Сервер API ничего не знает о ваших внутренних системах - он просто отвечает на отправленные вами запросы.
Вебхуки
Вебхуки (webhooks) означают, что сервер Elido отправляет событие на ваш HTTPS-эндпоинт. Ваш приемник обрабатывает его, возвращает 2xx, и доставка фиксируется как успешная. Сегодня эти события охватывают ссылки и домены (link.created, link.updated, link.expired, domain.verified и еще несколько). Событие click.created для отдельных кликов запланировано, но еще не отправляется, поэтому сейчас вебхук не срабатывает, когда кто-то кликает по ссылке.
Поток данных основан на «выталкивании» (push-based): платформа инициирует контакт. Ваш эндпоинт должен быть доступен из интернета, иметь TLS и надежно отвечать.
Когда поллинг - правильный выбор
Поллинг подходит для определенного набора условий. Если к вашей ситуации применимо большинство из нижеперечисленного, начните с поллинга и переходите к вебхукам только тогда, когда возникнет конкретная проблема.
Вы контролируете обе стороны интеграции. Когда потребителем является дашборд или инструмент отчетности, которым вы владеете и управляете, поллинг обеспечивает предсказуемое, ограниченное поведение. Вы сами определяете интервал, временное окно и способ обработки частичных результатов.
Ваш вариант использования - ретроспективный. Еженедельные отчеты по кампаниям, ежемесячные задачи агрегации и конвейеры сверки данных не выигрывают от субминутной задержки. Задачу cron, запускаемую каждый час для /summary или /breakdown/country, архитектурно проще реализовать и отлаживать, чем приемник вебхуков с сохранением состояния и обработкой повторных попыток.
У вас нет публичного эндпоинта. Вебхуки требуют наличия URL, доступного для инфраструктуры Elido. Если ваша интеграция работает внутри частной сети, в функции Lambda без стабильного URL или на локальной машине разработчика, настройка входящего эндпоинта HTTPS может обойтись дороже с точки зрения сложности эксплуатации, чем выгода от скорости получения данных.
Объем данных невелик. При нескольких тысячах кликов в день разница между реальным временем и пятиминутной задержкой редко заметна конечным пользователям. Поллинг прост для понимания, отладки и не преподносит сюрпризов в плане инфраструктуры.
Когда вебхуки - правильный выбор
Вебхуки имеют смысл, когда низкая задержка является требованием к продукту, а не просто приятным дополнением. Для данных о кликах этот раздел описывает план на тот момент, когда появится click.created; до этого те же сценарии работают на поллинге clicks/recent с коротким интервалом.
Вы создаете живой счетчик или real-time интерфейс. Если ваш продукт показывает пользователям количество кликов, которое обновляется в течение нескольких секунд после перехода по ссылке, поллинг с любым разумным интервалом будет казаться заметно устаревшим. Обработчик вебхуков, который увеличивает счетчик в Redis при событиях click.created и выводит его на фронтенд через соединение WebSocket или SSE, будет архитектурой, позволяющей добиться этого, когда такое событие появится. Сегодня ближайший к этому вариант - поллинг clicks/recent раз в минуту, который останавливается на последней уже обработанной строке.
Вы обогащаете записи в CRM для каждого клика. Привязка события клика к записи контакта - идентификация того, какой именно потенциальный клиент перешел по ссылке в вашем письме, и обновление его истории в CRM - критична ко времени. К тому времени, когда задача поллинга сработает через пять минут, менеджер по продажам уже может успеть позвонить клиенту. Обработчик вебхуков, который запускает обновление CRM в течение нескольких секунд после клика, будет правильным инструментом, когда события для отдельных кликов станут доступны; до этого сократите интервал поллинга.
Вы запускаете рабочие процессы, управляемые событиями (event-driven). Рабочие процессы, инициируемые событиями кликов - отправка последующего письма при переходе по ссылке, обновление сегмента подписчика, уменьшение количества товара на складе - являются естественными потребителями вебхуков. Планируемое событие click.created должно содержать достаточно данных, чтобы действовать немедленно, без дополнительного запроса. Рабочие процессы, инициируемые изменениями ссылок, например link.created или link.expired, уже могут использовать вебхуки.
У вас есть стабильный, публично доступный HTTPS-эндпоинт. Это обязательное условие, от которого зависит все остальное. Если у вас уже есть рабочая инфраструктура, принимающая входящие вебхуки от других провайдеров (Stripe, GitHub, Twilio), добавление Elido в тот же приемник не составит труда.
Скрытые расходы вебхуков
Вебхуки звучат просто: сервер отправляет POST, вы его обрабатываете. Реальная поверхность реализации намного шире.
Проверка подписи
Elido подписывает каждую доставку вебхука с помощью HMAC-SHA256. Формат подписи: v1=HMAC-SHA256(secret, "${unix_timestamp}.${body}"), передается в заголовках X-Elido-Signature и X-Webhook-Signature как псевдоним. Временная метка отправляется отдельно в X-Webhook-Timestamp.
Вы должны проверить эту подпись перед обработкой полезной нагрузки. Руководство по проверке подписей вебхуков также охватывает Python, 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 === "link.updated") {
// Handle the link event
console.log("link updated:", event.data);
}
// Всегда быстро возвращайте 2xx - тяжелую обработку делайте асинхронно
return res.status(200).json({ received: true });
});
Окно повтора
Проверка временной метки в приведенном выше примере обеспечивает то, что документация Elido называет «окном повтора» (replay window). Без нее злоумышленник, перехвативший одну валидную подписанную полезную нагрузку, может воспроизводить ее бесконечно - подпись остается действительной навсегда, так как она вычисляется на основе фиксированной временной метки. С проверкой полезная нагрузка старше пяти минут отклоняется независимо от того, валидна ли подпись.
Установите допуск на значение, которое может выдержать ваша инфраструктура. Пять минут - это стандартное значение по умолчанию, аналогичное тому, что использует Stripe. Если ваш приемник иногда отключается на несколько минут во время развертывания, это окно дает ему время вернуться в строй и все равно обработать запросы, находящиеся в пути.
Повторные попытки и идемпотентность
Elido повторяет неудачные доставки по графику: первая повторная попытка выполняется через 5 минут после сбоя, вторая - еще через 15 минут. Всего для одной доставки по умолчанию предусмотрено 3 попытки. После 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);
Ваш эндпоинт должен быть высокодоступным
Окно повторных попыток конечно. Если ваш приемник не работает более примерно 20 минут (5 + 15), попытки доставки будут исчерпаны, и они завершатся окончательной неудачей. Для событий, где гарантированная доставка важна - обогащение CRM, биллинг - инфраструктура вашего приемника нуждается в надлежащей доступности, а не в «домашнем» сервере, который периодически перезагружается.
Это наиболее недооцениваемая стоимость вебхуков для команд, впервые сталкивающихся с входящими HTTP-запросами. Поллинг деградирует изящно: если задача поллинга не удалась, она просто запустится снова в следующий интервал и нагонит упущенное. Приемник вебхуков, который недоступен, теряет события навсегда, если у вас нет стратегии сверки.
Скрытые расходы поллинга
Со стороны поллинг выглядит простым. Реальные затраты накапливаются в процессе эксплуатации.
Задержка (lag) - определяющее ограничение. Задача cron, работающая каждые пять минут, означает, что данные о кликах устарели на пять минут. Для большинства ретроспективных сценариев это приемлемо; для всего, что видит пользователь - нет. Сокращение интервала помогает, но не устраняет задержку, а очень короткие интервалы (менее минуты) начинают походить на «бомбардировку» API запросами, а не на поллинг.
Бесполезные запросы. В большинстве интервалов поллинга возвращаются те же данные, что и в предыдущем запросе. Если вы опрашиваете ссылку с низким трафиком каждую минуту, а клики происходят примерно раз в час, 59 из каждых 60 запросов не приносят ничего нового. Эти запросы все равно учитываются в лимитах API.
Лимиты скорости (rate limits). API Elido применяет лимиты скорости для каждого рабочего пространства (workspace), размер которых зависит от тарифного плана. Задача поллинга, которая часто выполняется для многих ссылок в большом рабочем пространстве, может достичь этих лимитов, особенно если другая автоматизация в том же пространстве также делает вызовы API. В этом случае API возвращает 429 Too Many Requests с заголовком X-RateLimit-Scope: workspace.
Пагинация и пропущенные события. Эндпоинт /clicks/recent использует пагинацию на основе курсора. Если вы выполняете поллинг в фиксированном временном окне - ?from=<last_poll>&to=<now> - и объем данных в этом окне превышает размер страницы, вы пропустите события, если не будете следовать за next_cursor по всем страницам. Реализация поллинга, которая не обрабатывает пагинацию, будет молча терять данные под нагрузкой.
Гибридный паттерн
Для большинства рабочих сценариев лучшим ответом является не выбор «или/или».
После появления click.created используйте вебхуки как основной путь для реакции в реальном времени: обновления CRM, живые счетчики, рабочие процессы, управляемые событиями. Задержка низкая; эксплуатационные расходы управляемы, если у вас уже есть инфраструктура для входящих HTTPS-запросов. До этого быстрый путь - поллинг clicks/recent с коротким интервалом, а описанная ниже сверка работает поверх него так же.
Используйте поллинг для еженедельной или ежедневной сверки: загрузите полный временной ряд за предыдущую неделю, сравните итоги с тем, что зафиксировал ваш обработчик вебхуков, и выявите любые пробелы. Это позволяет обнаружить доставки, которые исчерпали окно повторных попыток во время сбоя, события, которые пришли не по порядку, и любые расхождения между вашим локальным состоянием и источником истины Elido.
API аналитики хорошо подходит для этой роли. Эндпоинт /summary возвращает агрегированные итоги за диапазон дат в одном запросе; эндпоинт /timeseries возвращает данные по дням. Задача сверки, которая запускается раз в ночь и сравнивает количество кликов в вашей CRM с данными API за то же окно, может выявить проблемы с целостностью данных до того, как они станут заметны клиентам.
Cron для поллинга на Python
Поллинг - это то, с чего сегодня начинается любая интеграция кликов, и он продолжит работать после появления вебхуков для отдельных кликов. Ниже приведена минимальная реализация с использованием библиотеки schedule, которая вызывает clicks/recent с пятиминутным интервалом (подробно параметры курсора, даты и часового пояса рассмотрены в руководстве по API аналитики ссылок):
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)
В рабочей среде замените print на реальный приемник данных - запись в базу данных, вызов API CRM, публикацию в очереди сообщений - и добавьте обработку ошибок с экспоненциальной задержкой вокруг вызова requests.get.
Фильтрация ботов и что она значит для вашей интеграции
Одна деталь, которая влияет на оба паттерна: уровень редиректов Elido фильтрует клики ботов до их записи. Запросы от Googlebot, Bingbot, Slackbot, мониторов аптайма, curl, скриптовых библиотек и пустых User-Agent не учитываются как клики и не отображаются в результатах API аналитики, а после появления таких событий также не будут создавать события click.created.
Это важно, потому что ваш обработчик вебхуков или задача поллинга работают с количеством кликов от людей, а не с общим количеством HTTP-запросов. Если вы сопоставляете данные о кликах Elido с метриками на стороне сервера - логами вашего приложения или логами доступа CDN - ожидайте, что цифры Elido будут ниже. Это расхождение не является ошибкой; это работа фильтра ботов, удаляющего шум до того, как он попадет к вам.
Более подробную информацию о том, что охватывает фильтр ботов и как система оценки подозрительности помечает пограничный трафик, можно найти в руководстве по аналитике. О свойствах безопасности схемы подписи вебхуков - включая формат HMAC, привязку к временной метке и то, что она предотвращает - читайте в чек-листе по безопасности.
На странице с ценами приведена информация о том, какие тарифные планы включают эндпоинты вебхуков и каковы ограничения на объем доставки.
Похожее в блоге
- Почему мы используем ClickHouse для аналитики кликов (а не Postgres)
- Серверное отслеживание GA4 через уровень редиректов
- Достижение p95 < 15 мс для редиректов из FRA, ASH и SGP
- Объяснение бескокит-атрибуции: что все еще работает в 2026 году
- Проверка подписей вебхуков: HMAC-SHA256 в Node, Python и Go
- API аналитики ссылок: получение статистики кликов с помощью API-ключа
Попробуйте Elido
Вставьте URL - получите короткую ссылку
Без регистрации. Ссылка живёт 30 дней. Зарегистрируйтесь, чтобы оставить её навсегда.
Бесплатно, без регистрации · 2 в день