Вебхуки или опрос API: как системы узнают об изменениях
Вебхуки быстрее, но теряются. Опрос надёжнее, но отстаёт и грузит систему. Разбираем, когда какая модель уместна и как их сочетать.

Любая интеграция сводится к вопросу: как одна система узнаёт, что в другой что-то изменилось. Есть два ответа. Либо источник сам сообщает об изменении — вебхук. Либо получатель периодически спрашивает — опрос. Выбор влияет на задержку, нагрузку и на то, насколько тяжело будет разбираться, когда данные разойдутся.
Сравнение по существу
| Свойство | Вебхуки | Опрос API |
|---|---|---|
| Задержка | Секунды | Половина интервала опроса |
| Нагрузка | Пропорциональна числу событий | Постоянная, даже когда изменений нет |
| Надёжность | Событие теряется при недоступности получателя | Пропущенное подхватится следующим циклом |
| Сложность приёма | Нужен публичный адрес, подпись, защита от повторов | Достаточно расписания и метки времени |
| Поведение при сбое | Требует повторов на стороне источника | Самовосстанавливается |
Главное различие — не скорость, а поведение при сбое. Опрос прощает падение получателя: поднялся и догнал пропущенное. Вебхуки при падении просто теряют события, если источник не умеет повторять доставку — а умеет он далеко не всегда.
Как принимать вебхуки правильно
- Проверять подпись: без неё эндпоинт открыт для любого, кто узнал адрес
- Отвечать быстро и обрабатывать асинхронно: источники обычно ждут ответ не более 5–10 секунд
- Считать доставку возможной дважды: обработчик должен быть идемпотентным
- Логировать каждое поступление вместе с телом запроса
- Не полагаться на порядок: события приходят не в той последовательности, в которой произошли
export async function POST(request: Request) {
const raw = await request.text()
if (!verifyHmac(raw, request.headers.get("x-signature"))) {
return new Response("Invalid signature", { status: 401 })
}
const event = JSON.parse(raw) as { id: string; type: string }
// Уникальный индекс по event_id: повторная доставка не создаст
// вторую обработку, а вернёт тот же успешный ответ
const inserted = await db
.insert(webhookEvents)
.values({ eventId: event.id, type: event.type, payload: raw })
.onConflictDoNothing({ target: webhookEvents.eventId })
.returning({ id: webhookEvents.id })
if (inserted.length > 0) {
await queue.enqueue("webhook.handle", { id: inserted[0].id })
}
return Response.json({ ok: true })
}Гибридная схема
На ответственных интеграциях мы используем оба механизма одновременно. Вебхуки дают скорость, а редкий сверочный опрос закрывает пропуски. Такая схема стоит немного дороже, зато снимает целый класс жалоб вида «у нас в системе есть, а у вас нет».
// Раз в час запрашиваем изменения за период с запасом:
// перекрытие важнее экономии запросов
export async function reconcile() {
const since = minutesAgo(90)
const remote = await api.listChanges({ since })
let restored = 0
for (const item of remote) {
const known = await db.query.orders.findFirst({
where: eq(orders.externalId, item.id),
})
if (!known || known.externalUpdatedAt < item.updatedAt) {
await applyChange(item)
restored += 1
}
}
// Ненулевое значение — повод посмотреть, почему теряются вебхуки
if (restored > 0) reportMetric("integration.reconciled", restored)
}Показатель числа восстановленных при сверке записей полезен сам по себе: пока он около нуля, вебхуки работают. Если начал расти — что-то изменилось на стороне источника, и вы узнаете об этом раньше клиента.
Правило выбора
- Задержка в минуты допустима, событий мало — опрос, он дешевле и надёжнее
- Нужна реакция в секунды — вебхуки с подписью, очередью и защитой от повторов
- Данные критичны для денег или обязательств — вебхуки плюс регулярная сверка
- Источник не умеет вебхуки — опрос с постраничной выборкой по метке изменения
Частые вопросы
- Как часто можно опрашивать чужое API
- Смотрите на лимиты в документации, обычно это несколько запросов в секунду. Практически для большинства бизнес-задач достаточно интервала в 5–15 минут при выборке только изменившихся записей — так вы не приближаетесь к лимитам вообще.
- Что делать, если вебхуки приходят, а обработка падает
- Никогда не возвращать источнику ошибку после того, как событие сохранено. Правильная схема: принять, записать, ответить успехом, обрабатывать из очереди с повторами. Тогда сбой обработки не приводит к потере события.
- Нужен ли отдельный сервис для интеграций
- При двух-трёх интеграциях достаточно модуля в приложении. Отдельный сервис оправдан, когда систем больше пяти или когда обмен нужно масштабировать независимо от сайта.
Похожая задача в вашем проекте?
Разберём вашу ситуацию и пришлём оценку по этапам — без обязательств и общих слов.
Обсудить задачу

