Назад в блог

Синхронизация остатков в headless Bitrix: почему polling победил webhooks

У нас было три склада, 28K SKU и обещание клиенту: остатки в реальном времени.

Первые два месяца мы строили webhook-цепочку через Bitrix Events API. Потом я открыл логи и обнаружил: из 1000 событий долетало около 800. Остальные 200 исчезали где-то в очереди. Тихо, без алертов.

Это 20% пропущенных обновлений остатков. На складе с активным 1С-пополнением это уже не погрешность.

Мы переписали на polling. Потерянных обновлений с тех пор — ноль.

Почему первое решение — всегда webhooks

Логика железная. Изменился остаток в Bitrix, пришло событие, фронт обновился. Никакого лишнего трафика. Никаких запросов впустую. Настоящий real-time.

На бумаге — да. На нашем продакшне Bitrix Events API работал иначе.

Bitrix отправляет webhook синхронно, в момент изменения данных. Если ваш endpoint не ответил за 3 секунды — событие считается доставленным. Retry не гарантирован на уровне стандартного b24.userwebhook. Dead-letter очереди нет, мониторинга доставки — тоже.

На небольшом проекте с редкими обновлениями этого не видно. На 28K SKU с тремя складами и ежедневной 1С-синхронизацией — это системная потеря.

Как работает Bitrix Events API под нагрузкой

Когда 1С выгружает обновлённые остатки в Bitrix, iblock-элементы обновляются пачкой. Bitrix генерирует события для каждого элемента последовательно. При сотнях или тысячах одновременных обновлений очередь событий работает нестабильно.

Второй фактор — REST API rate limit. Стандартный тариф: 2 запроса в секунду на endpoint. Для polling батч-запросами это не критично. Но если вы планируете подтверждать каждое входящее webhook-событие запросом обратно к Bitrix — rate limit становится настоящим ограничением.

Третий фактор: если ваш endpoint принимает события, но пишет в Redis под нагрузкой, и Redis отвечает медленнее 3 секунд, Bitrix не будет ждать. Событие потеряно.

Это не баг Bitrix. Это ограничение архитектуры, о котором не пишут в туториалах.

Три паттерна синхронизации остатков

Я выписал три рабочих подхода.

Webhooks как основной канал

Bitrix отправляет событие при изменении остатка → endpoint принимает, пишет в Redis → Next.js читает при рендере. Быстро. Почти real-time.

Но без надёжной очереди (Redis Streams, RabbitMQ) работает только при низкой нагрузке. С очередью — уже production-ready, но добавляется целый инфраструктурный слой: retry-логика, dead-letter, алерты на задержку.

Подходит, если у вас есть команда, готовая эту инфраструктуру поддерживать.

Polling

Cron каждые 60–120 секунд запрашивает Bitrix REST API (CatalogProduct.list с фильтром по DATE_MODIFIED) → результаты пишутся в Redis → Next.js читает при ISR-рендере или через revalidateTag.

Webhook endpoint и очередь не нужны. Потерянных событий — ноль. Bitrix REST rate limit 2 req/sec достаточен: батч-запрос по 50 товаров с пагинацией обрабатывает сотни обновлений за несколько секунд.

Задержка — до 60–120 секунд. Это приемлемо для большинства B2B e-commerce.

Hybrid

Webhook триггерит revalidatePath или revalidateTag для быстрого обновления конкретной страницы. Polling каждые 60–120 секунд страхует пропущенные события и восстанавливает eventual consistency.

Webhook даёт скорость там, где она важна. Polling гарантирует, что ничего не потеряно.

Почему 60 секунд — это честный ответ

«Polling раз в 60 секунд» звучит как компромисс. Но это не компромисс — это правильный ответ при правильных требованиях.

Хорошо, но нужен ли покупателю реальный real-time?

Сценарий из жизни: покупатель открывает карточку товара. Если остаток изменился 30 секунд назад, он всё равно увидит актуальную цифру на следующей загрузке. Критично это только для flash-продаж с обратным таймером. Для стандартного B2B каталога с ежедневным пополнением — нет.

Ещё один аргумент: ISR в Next.js и polling стыкуются хорошо. revalidateTag из cron-задачи — это ровно то, для чего ISR проектировался. Страница обновляется максимум через N секунд после изменения данных. Предсказуемо. Мониторируемо. Подробнее про ISR и инвалидацию кеша мы писали отдельно — там про кеш контента, а не остатков, но логика та же.

Архитектура в продакшне

На нашем проекте это выглядит так:

Bitrix (1С-синхронизация → iblock) → cron каждые 60 секунд на сервере → CatalogProduct.list с фильтром по DATE_MODIFIED >= now() - 120s → Redis Hash (sku → quantity) → Next.js читает при ISR-рендере.

Дополнительно: если настроен webhook endpoint, он вызывает revalidatePath для топ-категорий при каждом входящем событии. Это ускоряет обновление, но webhook здесь вспомогательный, не основной.

Поле DATE_MODIFIED в iblock обновляется стандартным образом при изменении остатков. Это не кастомный механизм.

Мониторинг: cron пишет JSONL-лог с временем последнего успешного poll и количеством обновлённых записей. Если три poll подряд завершились с ошибкой — Telegram-алерт.

Что мы измерили

До перехода на polling:

  • Потери webhook-событий: ~20% (200 из 1000 за неделю мониторинга)
  • Обнаружено только при ручном аудите, алертов не было
  • Восстановление: full-sync раз в сутки по расписанию

После перехода на polling + hybrid:

  • Потерь: 0
  • Максимальная задержка обновления: 60 секунд
  • Инфраструктура: Redis + cron, без дополнительной очереди

LCP не изменился. Клиент не заметил 60-секундную задержку и не должен был.

Когда polling не подходит

Polling перестаёт быть достаточным в трёх случаях.

Первое — flash-продажи с обратным таймером и требованием «именно сейчас». Здесь нужен webhook + надёжная очередь с гарантией доставки.

Второе — тысячи товаров обновляются одновременно из нескольких источников, и REST API rate limit становится настоящим ограничением, а не теоретическим.

Третье — жёсткий SLA на обновление менее 30 секунд, продиктованный бизнесом, а не привычкой.

В этих случаях нужна полноценная очередь. Про ограничения Bitrix REST API в headless — отдельная история, там несколько неочевидных моментов.

Checklist выбора паттерна

Polling достаточен при обновлениях до 1000 записей в час и приемлемой задержке 60–120 секунд. Если команда не хочет поддерживать очередь — polling и есть правильный выбор.

Hybrid имеет смысл, когда нужно мгновенное обновление для топ-категорий: один-два дня настройки webhook endpoint плюс polling как backstop. Именно это мы и выбрали.

Full webhook + очередь — только если у вас flash-продажи с жёсткими временными окнами или команда с опытом работы с очередями в продакшне. Без этого опыта проще не будет.

Мы выбрали hybrid. Polling страхует от потерь. Webhook ускоряет там, где нужно. Инфраструктура простая, потерянных обновлений — ноль.