Два тижні тому один магазин прийшов до нас із проблемою, яка спочатку виглядала дрібницею: раз на кілька днів на складі «губилося» замовлення. Не критично — три-чотири штуки на тиждень. Але клієнт телефонує, менеджер шукає замовлення в CRM, знаходить, а на складі його ніхто не бачив. З’ясувалось: інтеграція між магазином і складською системою тихо падала кожні кілька годин, і ніхто про це не знав, бо помилка просто губилась у логах, які ніхто не читав.
Це типова історія. Інтеграція через API виглядає простою на етапі документації: ось endpoint, ось токен, ось приклад запиту. Проблеми починаються не в момент підключення, а через місяць-два експлуатації, коли обидві системи живуть своїм життям і одна з них падає, гальмує або віддає щось несподіване.
Webhooks чи опитування: не завжди очевидний вибір
Перше рішення при будь-якій інтеграції — як система дізнається про зміни в іншій. Варіантів фактично два: чекати, поки зовнішня система сама надішле подію (webhook), або періодично питати самому «що нового» (polling).
Webhook виглядає елегантніше. Подія сталась — прилетів запит, обробили, все миттєво. Але тут є нюанс: webhook працює тільки якщо приймаюча сторона доступна саме в момент відправки. Сервер впав на 40 секунд для деплою — і подія, яка прилетіла саме в ці 40 секунд, просто зникла. Більшість систем не повторюють відправку, або повторюють один-два рази й здаються.
Опитування навпаки — негарне, зате передбачуване. Раз на п’ять хвилин запит «дай мені все, що змінилось з такого часу» завжди наздожене пропущені зміни, бо наступний запит просто підхопить те, що не встиг попередній. Ціна — затримка (дані не миттєві) і навантаження на обидві системи, особливо якщо каталог великий.
На практиці робочою схемою частіше виявляється комбінація: webhook для швидкої реакції плюс регулярна polling-звірка раз на годину чи раз на добу, яка ловить те, що webhook міг втратити. Здається дублюванням роботи. Насправді це страховка, яка коштує кількох рядків коду і рятує від ситуацій на кшталт тієї, з якої почалась ця стаття.

Чому один і той самий запит не повинен створювати два замовлення
Мережа ненадійна за визначенням. Запит пішов, сервер його обробив, замовлення створилось у CRM — а відповідь до магазину не дійшла через таймаут. Магазин бачить помилку і, за логікою retry, надсилає той самий запит ще раз. CRM отримує другий запит і — якщо нічого не зроблено спеціально — створює друге замовлення. Клієнту дзвонять двічі, склад списує товар двічі, бухгалтерія плутається в звітах.
Рішення називається ідемпотентність, і насправді воно просте: кожному запиту, який щось змінює (створює замовлення, списує товар, оновлює баланс), присвоюється унікальний ключ — ідемпотентний ключ. CRM перед обробкою перевіряє: цей ключ вже приходив? Якщо так — повертає результат попередньої обробки, нічого не створюючи вдруге. Якщо ні — обробляє і запам’ятовує ключ.
Хто генерує ключ? Той, хто ініціює запит, і генерувати його треба один раз — на етапі формування замовлення, а не на етапі кожної спроби відправки. Інакше retry просто згенерує новий ключ і проблема повернеться в тому ж вигляді.
Retry — це не просто «спробувати ще раз»
Наївний retry — коли після помилки система одразу повторює запит — на перший погляд виглядає розумно. На практиці він здатний покласти вже і так перевантажений сервер: той відповідає повільно, отримує ще один запит, відповідає ще повільніше, і за кілька хвилин черга запитів росте лавиноподібно.
Робочий підхід — експоненційна затримка між спробами: перша повторна спроба через секунду, наступна через дві, потім чотири, вісім і так далі, з якимось розумним максимумом і невеликим випадковим розкидом (jitter), щоб сотні клієнтів не били по серверу рівно в одну секунду одночасно.
І окремо — що робити, коли спроби скінчились, а результату немає. Тут з’являється черга непридатних повідомлень, dead-letter queue: замість того, щоб просто загубити подію після п’ятої невдалої спроби, її кладуть в окрему чергу для ручного розбору. Хтось із команди раз на день переглядає цю чергу і вирішує — повторити, пропустити чи розібратись вручну. Без такої черги подія просто зникає, і ви дізнаєтесь про пропущене замовлення від клієнта, а не від системи.

Хто головний, коли дані розходяться
Уявімо: клієнт змінив номер телефону в CRM під час дзвінка менеджеру. Через годину той самий клієнт змінив телефон у себе в кабінеті на сайті. Яке значення правильне?
Без чіткого правила відповідь залежить від того, яка синхронізація відпрацювала останньою — а це випадковість, не логіка. Тому перед тим, як писати код, варто прямо визначити: яка система є джерелом правди для яких даних. Найчастіше це виглядає так — CRM головна для статусу замовлення й історії спілкування з клієнтом, магазин головний для каталогу і цін, склад головний для залишків. Клієнтські дані (телефон, адреса) — найчастіше найбільш спірна зона, і тут варто явно вирішити: останній за часом запис виграє, чи один бік взагалі не приймає зміни з іншого.
Це рішення не технічне, а організаційне. Розробник може реалізувати будь-яку логіку, але визначити, хто головний, повинен той, хто розуміє бізнес-процес — умовно, чи має право сайт «тихо» переписати те, що менеджер щойно ввів руками під час розмови з клієнтом.
Як зрозуміти, що синхронізація тихо зламалась
Найгірший сценарій — не той, коли інтеграція падає з гучною помилкою. Найгірший — коли вона продовжує «працювати», просто нічого не роблячи: endpoint повертає 200 OK, чергу обробляє, а дані насправді вже кілька днів не оновлюються, тому що змінився формат відповіді на боці постачальника, чи протух токен, чи хтось випадково поміняв URL.
З цим ми стикаємось регулярно на аудитах: клієнт впевнений, що інтеграція працює, бо «помилок ніхто не бачив» — а насправді залишки на сайті не оновлювались три тижні.
- Лічильник останнього успішного синку з таймстампом — і алерт, якщо він старіший за очікуваний інтервал.
- Моніторинг не тільки помилок, а й «підозрілої тиші» — різке падіння кількості оброблених подій теж сигнал.
- Періодична звірка контрольних цифр: скільки замовлень у магазині за добу і скільки за той самий період прийшло в CRM.
Жодна з цих речей не складна технічно. Складність у тому, щоб хтось узагалі про це подумав до того, як інтеграція пішла в продакшн, а не після того, як клієнт втретє поскаржився на загублене замовлення.
Наостанок
Інтеграція, яка технічно «працює» в день запуску, і інтеграція, яка витримує рік реального навантаження, — це різні рівні роботи. Різниця не в тому, скільки ендпоінтів підключено, а в тому, що станеться, коли один з них на хвилину стане недоступним, чи два запити прийдуть одночасно, чи хтось на іншому боці змінить формат даних без попередження. Якщо ці сценарії продумані заздалегідь — інтеграція просто працює роками, тихо і непомітно, що, зрештою, і є метою.