Две команды, создающие интеграцию на базе одного и того же API сокращателя ссылок, часто приходят к совершенно разным архитектурам. Одна команда настраивает эндпоинт для вебхуков и реагирует на каждый клик в режиме реального времени. Другая пишет задачу cron, которая опрашивает API аналитики каждые пять минут. Оба варианта допустимы. Выбор между ними имеет реальные последствия для задержки (latency), эксплуатационных расходов и того, насколько ваша система будет деградировать при возникновении проблем.
В этом посте представлены основные компромиссы.
Два паттерна#
Поллинг#
Поллинг (polling) означает, что ваш код запрашивает у API последние данные о кликах по расписанию. Задача cron просыпается, вызывает /v1/analytics/workspaces/{id}/clicks/recent или /v1/analytics/workspaces/{id}/summary, обрабатывает результаты и засыпает до следующего интервала.
Поток данных основан на «вытягивании» (pull-based): ваша инфраструктура инициирует каждое взаимодействие. Сервер API ничего не знает о ваших внутренних системах - он просто отвечает на отправленные вами запросы.
Вебхуки#
Вебхуки (webhooks) означают, что сервер Elido отправляет событие click.recorded на ваш HTTPS-эндпоинт вскоре после обработки клика. Ваш приемник обрабатывает его, возвращает 2xx, и доставка фиксируется как успешная.
Поток данных основан на «выталкивании» (push-based): платформа инициирует контакт. Ваш эндпоинт должен быть доступен из интернета, иметь TLS и надежно отвечать.
Когда поллинг - правильный выбор#
Поллинг подходит для определенного набора условий. Если к вашей ситуации применимо большинство из нижеперечисленного, начните с поллинга и переходите к вебхукам только тогда, когда возникнет конкретная проблема.
Вы контролируете обе стороны интеграции. Когда потребителем является дашборд или инструмент отчетности, которым вы владеете и управляете, поллинг обеспечивает предсказуемое, ограниченное поведение. Вы сами определяете интервал, временное окно и способ обработки частичных результатов.
Ваш вариант использования - ретроспективный. Еженедельные отчеты по кампаниям, ежемесячные задачи агрегации и конвейеры сверки данных не выигрывают от субминутной задержки. Задачу cron, запускаемую каждый час для /summary или /breakdown/country, архитектурно проще реализовать и отлаживать, чем приемник вебхуков с сохранением состояния и обработкой повторных попыток.
У вас нет публичного эндпоинта. Вебхуки требуют наличия URL, доступного для инфраструктуры Elido. Если ваша интеграция работает внутри частной сети, в функции Lambda без стабильного URL или на локальной машине разработчика, настройка входящего эндпоинта HTTPS может обойтись дороже с точки зрения сложности эксплуатации, чем выгода от скорости получения данных.
Объем данных невелик. При нескольких тысячах кликов в день разница между реальным временем и пятиминутной задержкой редко заметна конечным пользователям. Поллинг прост для понимания, отладки и не преподносит сюрпризов в плане инфраструктуры.
Когда вебхуки - правильный выбор#
Вебхуки имеют смысл, когда низкая задержка является требованием к продукту, а не просто приятным дополнением.
Вы создаете живой счетчик или real-time интерфейс. Если ваш продукт показывает пользователям количество кликов, которое обновляется в течение нескольких секунд после перехода по ссылке, поллинг с любым разумным интервалом будет казаться заметно устаревшим. Обработчик вебхуков, который увеличивает счетчик в 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. Временная метка отправляется отдельно в 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 });
});
Окно повтора#
Проверка временной метки в приведенном выше примере обеспечивает то, что документация Elido называет «окном повтора» (replay window). Без нее злоумышленник, перехвативший одну валидную подписанную полезную нагрузку, может воспроизводить ее бесконечно - подпись остается действительной навсегда, так как она вычисляется на основе фиксированной временной метки. С проверкой полезная нагрузка старше пяти минут отклоняется независимо от того, валидна ли подпись.
Установите допуск на значение, которое может выдержать ваша инфраструктура. Пять минут - это стандартное значение по умолчанию, аналогичное тому, что использует 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-запросами. Поллинг деградирует изящно: если задача поллинга не удалась, она просто запустится снова в следующий интервал и нагонит упущенное. Приемник вебхуков, который недоступен, теряет события навсегда, если у вас нет стратегии сверки.
Скрытые расходы поллинга#
Со стороны поллинг выглядит простым. Реальные затраты накапливаются в процессе эксплуатации.
Задержка (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 по всем страницам. Реализация поллинга, которая не обрабатывает пагинацию, будет молча терять данные под нагрузкой.
Гибридный паттерн#
Для большинства рабочих сценариев лучшим ответом является не выбор «или/или».
Используйте вебхуки как основной путь для реакции в реальном времени: обновления 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.
Фильтрация ботов и что она значит для вашей интеграции#
Одна деталь, которая влияет на оба паттерна: сервис редиректов Elido фильтрует клики ботов перед отправкой событий кликов в конвейер обработки. Запросы от Googlebot, Bingbot, Slackbot, мониторов аптайма, curl, скриптовых библиотек и пустых User-Agent не создают событий click.recorded и не отображаются в результатах API аналитики.
Это важно, потому что ваш обработчик вебхуков или задача поллинга работают с количеством кликов от людей, а не с общим количеством HTTP-запросов. Если вы сопоставляете данные о кликах Elido с метриками на стороне сервера - логами вашего приложения или логами доступа CDN - ожидайте, что цифры Elido будут ниже. Это расхождение не является ошибкой; это работа фильтра ботов, удаляющего шум до того, как он попадет к вам.
Более подробную информацию о том, что охватывает фильтр ботов и как система оценки подозрительности помечает пограничный трафик, можно найти в руководстве по аналитике. О свойствах безопасности схемы подписи вебхуков - включая формат HMAC, привязку к временной метке и то, что она предотвращает - читайте в чек-листе по безопасности.
На странице с ценами приведена информация о том, какие тарифные планы включают эндпоинты вебхуков и каковы ограничения на объем доставки.
Похожее в блоге#
Попробуйте Elido
Вставьте URL - получите короткую ссылку
Без регистрации. Ссылка живёт 30 дней. Зарегистрируйтесь, чтобы оставить её навсегда.
Бесплатно, без регистрации · 2 в день