Синхронизация остатков в 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 ускоряет там, где нужно. Инфраструктура простая, потерянных обновлений — ноль.