Интеграции и API

Как происходит интеграция сайта с 1С, CRM и внешними API

Фраза «нужно интегрировать сайт с 1С» часто занимает одну строку технического задания.

На схеме всё выглядит ещё проще:

Сайт ↔ 1С

Или:

Сайт ↔ CRM

Иногда рядом появляется третья стрелка:

Сайт ↔ API сервиса

Из-за этой простоты легко представить интеграцию как несколько HTTP-запросов: отправили JSON, получили ответ, сохранили данные — готово.

В реальном проекте самое интересное начинается как раз после того, как первый успешный запрос уже работает.

Что произойдёт, если 1С недоступна десять минут? Кто является главным источником цены товара? Что делать, если CRM получила одну заявку дважды? Как сопоставить клиента на сайте с контрагентом в 1С? Должен ли заказ на сайте ждать ответа учётной системы? Что произойдёт, если внешнее API приняло запрос, но соединение оборвалось до получения ответа? Как восстановить обмен после трёх часов недоступности одной из систем?

Именно ответы на такие вопросы отличают промышленную интеграцию от демонстрации, которая прекрасно работает на ноутбуке разработчика и начинает создавать проблемы после запуска.

Разберём, как на самом деле проектируется связь сайта с 1С, CRM и внешними API и почему хорошая интеграция — это прежде всего работа с состояниями, ответственностью и отказами.


Интеграция начинается не с API

До выбора протокола полезно ответить на более простой вопрос:

какие данные принадлежат каждой системе?

Представим интернет-магазин.

Сайт знает о посетителях, корзине, пользовательской сессии и содержимом оформляемого заказа.

1С хранит номенклатуру, цены, остатки, контрагентов, документы реализации и данные бухгалтерского или управленческого учёта.

CRM работает с лидами, менеджерами, сделками, коммуникациями и воронкой продаж.

Получается уже не просто:

Сайт ↔ 1С ↔ CRM

А система с разной ответственностью:

             ┌──────────────┐
             │     Сайт     │
             │              │
             │ Корзина      │
             │ Checkout     │
             │ Аккаунт      │
             └──────┬───────┘
                    │
        ┌───────────┴───────────┐
        ↓                       ↓
┌──────────────┐         ┌──────────────┐
│      1С      │         │     CRM      │
│              │         │              │
│ Номенклатура │         │ Лиды         │
│ Цены         │         │ Сделки       │
│ Остатки      │         │ Менеджеры    │
│ Документы    │         │ Коммуникации │
└──────────────┘         └──────────────┘

До написания интеграционного кода необходимо определить, кто отвечает за каждое конкретное значение.

Иначе через некоторое время одновременно появятся три разные цены одного товара: одна на сайте, другая в CRM, третья в 1С.


Один из главных терминов интеграции — источник истины

Допустим, менеджер изменил цену товара в 1С.

Одновременно администратор сайта имеет возможность изменить ту же цену в CMS.

Какое значение считать правильным?

Если ответ звучит как «которое обновили последним», система уже потенциально нестабильна.

Намного надёжнее заранее установить правило:

Цена → источник истины: 1С
Описание → источник истины: CMS
Остаток → источник истины: 1С
Заявка → создаётся на сайте
Сделка → ведётся в CRM
Статус оплаты → платёжная система / backend

Тогда направление синхронизации становится очевиднее.

Например:

1С
 │
 ├── цена ─────────────→ Сайт
 │
 └── остаток ──────────→ Сайт

Сайт
 │
 └── заказ ────────────→ 1С

Сайт
 │
 └── заявка ───────────→ CRM

CRM
 │
 └── статус сделки ────→ Сайт

Это уже архитектура обмена, а не просто набор API-вызовов.

Одна из самых дорогих интеграционных ошибок — позволить нескольким системам независимо редактировать одни и те же данные без понятных правил разрешения конфликтов.


«Передавать клиентов» — недостаточное требование

Рассмотрим aparentemente простую формулировку:

Передавать клиентов с сайта в CRM.

Разработчику всё ещё неизвестно, как должна работать система.

Пользователь отправил первую заявку:

Иван Петров
+7 900 000-00-00
ivan@example.ru

Создаём клиента.

Через неделю он отправляет новую заявку с тем же email, но с другого телефона.

Создавать второго клиента?

Обновить первого?

Создать новую сделку у существующего контакта?

А если email одинаковый, но имя другое?

А если заявка поступила от корпоративного сотрудника и email принадлежит всей компании?

Поэтому задача интеграции быстро превращается в задачу идентификации сущностей.

Нужно заранее определить ключи сопоставления.

Например:

User ID сайта
        ↓
External ID CRM
        ↓
External ID 1С

Вместо постоянных попыток угадать человека по имени и телефону системы получают устойчивые внешние идентификаторы.


Хорошая интеграция почти всегда хранит связь идентификаторов

У одного заказа могут существовать разные ID:

Сайт:
order_id = 1842

CRM:
deal_id = 73194

1С:
document_id = 91bfe...

Полезно хранить соответствие:

Integration mapping

site_order_id: 1842
crm_deal_id: 73194
one_c_document_id: 91bfe...

Тогда приложение знает, какой объект необходимо обновить.

Без этого спустя несколько месяцев код начинает искать сущности по имени клиента, номеру телефона, номеру заказа или другим косвенным признакам.

На небольшом объёме данных это ещё может работать.

На реальной базе появляются дубликаты.


Как сайт технически может работать с 1С

У современной платформы 1С:Предприятие есть несколько механизмов интеграции.

Один вариант — собственный HTTP-сервис внутри 1С.

Тогда внешний сайт обращается к специально разработанным endpoint:

GET /api/products/184

или:

POST /api/orders

А код на стороне 1С решает, какие данные вернуть и какие действия выполнить.

Другой вариант — стандартный REST-интерфейс 1С на базе OData. Он позволяет внешней системе обращаться к опубликованным объектам информационной базы.

Существуют также web-сервисы, а сама 1С умеет выполнять исходящие HTTP(S)-запросы к внешним системам.

Поэтому интеграция не обязательно означает, что сайт постоянно «ходит в 1С». Направление взаимодействия зависит от задачи.

Например:

Сайт ───────HTTP──────→ 1С

или:

1С ───────HTTP──────→ Backend сайта

или двусторонняя схема:

Сайт ←──────────────→ 1С

Технически реализовать можно все три подхода.

Правильный вариант определяется бизнес-процессом.


Почему открывать наружу всю структуру 1С не всегда лучшая идея

Автоматический интерфейс удобен, когда внешней системе действительно требуется работать непосредственно с определёнными объектами.

Но бизнес-операция далеко не всегда равна простой записи строки в справочник.

Представим действие:

Создать заказ клиента.

В реальности могут потребоваться проверка контрагента, определение организации, склада, договора, вида цены, валюты, налоговых параметров, резервирование товара и запуск внутренних правил конфигурации.

В таком случае специализированный endpoint:

POST /integration/orders

может оказаться надёжнее, чем предоставление внешнему приложению возможности самостоятельно собирать внутренние объекты 1С.

Внешняя система говорит:

{
  "externalOrderId": "WEB-1842",
  "customer": {
    "externalId": "USR-482"
  },
  "items": [
    {
      "sku": "A-184",
      "quantity": 2
    }
  ]
}

А уже интеграционный слой 1С преобразует запрос во внутреннюю бизнес-операцию.

Так внутреннее устройство учётной системы меньше «протекает» наружу.


Сайт не должен знать внутренности 1С лучше самой 1С

Это полезный архитектурный принцип.

Плохая интеграция часто выглядит так:

Backend сайта знает:

какой справочник использовать;
какое перечисление соответствует статусу;
как создавать документ;
какие реквизиты обязательны;
в какой регистр нужно записать данные.

Теперь любое изменение конфигурации 1С потенциально требует обновления сайта.

Получается сильная связанность двух систем.

Предпочтительнее контракт уровня бизнеса:

Сайт:

«Создай заказ с такими товарами»

а не:

Сайт:

«Создай элемент X,
затем запись Y,
после этого измени регистр Z».

Чем меньше внешняя система зависит от внутренних деталей другой системы, тем дешевле обе развивать.


Интеграция с CRM устроена похожим образом

Допустим, посетитель отправляет форму:

Имя: Алексей
Телефон: ...
Email: ...
Услуга: разработка CRM
Комментарий: ...

Наивная реализация:

Форма
  ↓
POST CRM API
  ↓
Создать лид

Работает.

Пока CRM доступна.

Что произойдёт, если API CRM не отвечает?

Если backend сайта сделан неудачно, посетитель получает:

Не удалось отправить форму.

Хотя проблема находится не на сайте и данные клиента вполне можно сохранить локально.

Для бизнеса это особенно неприятный вариант: работа лид-формы теперь напрямую зависит от доступности сторонней CRM.


Надёжнее сначала сохранить важные данные у себя

Для критичных операций часто подходит другая схема:

Пользователь отправил заявку
            ↓
Backend сайта
            ↓
Сохранить заявку в собственной БД
            ↓
Вернуть пользователю успех
            ↓
Создать задачу синхронизации
            ↓
CRM API

Если CRM работает — заявка появляется там через секунды.

Если CRM временно недоступна — заявка остаётся сохранённой на сайте и будет отправлена повторно.

Пользователь не должен заполнять форму второй раз.

А бизнес не теряет лид.

Это одно из главных различий между интеграцией «API отвечает» и интеграцией, пригодной для production.


Для чего нужна очередь

Между сайтом и внешней системой часто появляется очередь задач:

Сайт
  ↓
Database
  ↓
Queue
  ↓
Integration Worker
  ↓
CRM / 1С / API

Допустим, CRM отвечает ошибкой.

Worker не уничтожает задачу.

Он может повторить попытку:

1-я попытка → сейчас
2-я → через 1 минуту
3-я → через 5 минут
4-я → через 30 минут

Интервалы и количество попыток зависят от конкретной операции.

Если проблема остаётся, задача может перейти в специальное состояние:

FAILED

или в dead-letter queue.

Администратор видит ошибку и может повторить операцию после устранения причины.

Получается очень важная вещь:

временная недоступность внешней системы перестаёт автоматически превращаться в потерю данных.


Но повторный запрос создаёт новую проблему

Представим следующую ситуацию.

Сайт отправил CRM команду:

Создать сделку

CRM создала сделку.

Но соединение оборвалось раньше, чем сайт получил ответ.

С точки зрения сайта операция не завершилась.

Worker делает повторную попытку:

Создать сделку

В CRM появляется вторая одинаковая сделка.

Так возникает классический интеграционный дубль.

Поэтому критичные операции желательно делать идемпотентными.


Идемпотентность — одно действие, даже если запрос пришёл несколько раз

Можно передать уникальный идентификатор операции:

Idempotency-Key: 85f04a8e-...

или использовать стабильный внешний ID объекта:

{
  "externalOrderId": "WEB-1842"
}

Получающая система сначала проверяет:

Такой заказ уже существует?

Если да — не создаёт второй, а возвращает информацию о существующем.

Получается:

Запрос №1
WEB-1842
    ↓
Создан заказ 782

Запрос №2
WEB-1842
    ↓
Заказ уже существует
    ↓
Вернуть 782

Повторная доставка становится безопасной.

В распределённых системах это очень важное свойство, потому что гарантировать отсутствие повторов значительно сложнее, чем научиться правильно их обрабатывать.


Webhook решает обратную задачу

До сих пор сайт сам обращался к внешней системе.

Но иногда нужно, чтобы внешняя система сообщала о произошедшем событии.

Например, сделка в CRM получила статус:

Оплачено

CRM отправляет:

POST /webhooks/crm

с событием:

{
  "eventId": "evt_9182",
  "type": "deal.status_changed",
  "dealId": "73194",
  "status": "paid"
}

Backend сайта принимает событие и обновляет соответствующий объект.

Получается:

CRM
 ↓ webhook
Backend
 ↓
Database
 ↓
Клиентский кабинет

Пользователь почти сразу видит новое состояние.


Webhook тоже может прийти дважды

Это важно.

Многие внешние системы используют модель доставки «как минимум один раз».

То есть одно событие потенциально может поступить повторно.

Поэтому обработчик должен хранить eventId:

Получили evt_9182
        ↓
Уже обрабатывали?
   ↙             ↘
 да              нет
 ↓                ↓
200 OK         обработать
                   ↓
              сохранить evt_9182

Повторное событие становится безопасным.

Именно поэтому фраза:

CRM присылает webhook

ещё не описывает готовую интеграцию.


Подпись webhook нужно проверять

Представим публичный адрес:

https://example.ru/api/webhooks/payment

Если backend принимает любой JSON, злоумышленник может самостоятельно отправить:

{
  "status": "paid"
}

Поэтому поддерживающие такую возможность провайдеры обычно подписывают webhook секретом.

Backend проверяет подпись и только после этого доверяет сообщению.

Кроме подписи могут использоваться временные метки, защита от replay-атак и другие механизмы.

Доверять внешнему событию только потому, что оно пришло на «секретный URL», обычно недостаточно.


Самая интересная проблема — двусторонняя синхронизация

Односторонний обмен относительно понятен:

1С → Сайт

Значительно сложнее:

1С ↔ Сайт

Представим карточку клиента.

В 10:00 менеджер меняет телефон в 1С.

В 10:01 сам клиент меняет телефон в личном кабинете.

Теперь существуют два новых значения.

Что делать?

Варианты могут быть разными.

Можно объявить 1С единственным источником истины.

Можно разрешить сайту обновлять контактные данные, после чего отправлять их в 1С.

Можно использовать версии объекта.

Можно разрешить разные системы изменять разные поля.

Например:

ФИО              → CRM
Телефон клиента  → сайт
Юр. реквизиты    → 1С
Маркетинговые
настройки         → сайт

Важно не то, какой вариант выбран.

Важно, чтобы правило существовало до появления первого конфликта.


«Последний изменивший победил» звучит проще, чем работает

Иногда используется стратегия last write wins.

Последняя запись считается правильной.

Но тут появляются часы серверов, задержка очереди и события, пришедшие не по порядку.

Например:

10:00 изменение A
10:01 изменение B

Из-за временной недоступности сети события приходят так:

10:01 → B
10:04 → A

Если смотреть только на время получения, старое значение A затрёт новое B.

Поэтому при серьёзной двусторонней синхронизации могут понадобиться версия объекта, время фактического изменения или собственная стратегия конфликтов.


События вообще не обязаны приходить по порядку

Представим заказ:

CREATED
   ↓
PAID
   ↓
SHIPPED

В нормальной ситуации события приходят именно так.

Но из-за повторов, очередей и задержек backend может получить:

SHIPPED
PAID

Если код слепо присваивает полученный статус, отправленный заказ внезапно становится просто оплаченным.

Поэтому state machine полезна не только внутри основного приложения.

Интеграционный слой тоже должен понимать допустимые переходы:

CREATED → PAID       ✓
PAID → SHIPPED       ✓
SHIPPED → PAID       ✗

Так интеграционная ошибка не повреждает бизнес-состояние.


Пример: сайт интернет-магазина и 1С

Рассмотрим более реалистичный сценарий.

В 1С находятся:

Номенклатура
Цены
Остатки
Заказы
Контрагенты

На сайте:

Карточки товаров
Поиск
Корзина
Checkout
Личный кабинет

Один из вариантов обмена выглядит так:

                 1С
                 │
        ┌────────┼────────┐
        ↓        ↓        ↓
      Цена     Остаток  Номенклатура
        │        │        │
        └────────┼────────┘
                 ↓
               Сайт
                 │
                 │ заказ
                 ↓
                 1С

Но даже эта схема ещё слишком простая.


Нужно ли передавать весь каталог при каждом изменении?

Допустим, в базе 100 000 товаров.

Каждые пять минут выгружать все 100 000 объектов — не самое экономичное решение.

Можно использовать инкрементальный обмен:

Последняя успешная синхронизация:
21.09.2026 14:00

Получить объекты,
изменённые после 14:00

Или событийную модель, при которой изменившийся объект попадает в очередь обмена.

Так вместо полного каталога передаются только изменения.

Однако периодическая полная сверка всё равно может быть полезна как механизм восстановления консистентности.


Быстрый обмен не обязательно должен быть единственным

Хорошая интеграция иногда использует два режима одновременно.

Основной:

Изменение
   ↓
Быстрая инкрементальная синхронизация

Страховочный:

Раз в ночь
   ↓
Сверка состояния
   ↓
Обнаружение расхождений
   ↓
Исправление / отчёт

Это особенно полезно для данных, потеря которых критична.

Real-time обмен отвечает за скорость.

Reconciliation — за уверенность, что системы в итоге пришли к согласованному состоянию.


Остаток товара — хороший пример сложного значения

Кажется, что нужно просто передать:

stock = 12

Но что означает 12?

Физически находится на складе?

Доступно для продажи?

Уже зарезервировано?

Ожидается от поставщика?

Есть на двух складах?

Допустимо продавать в минус?

Например:

Физический остаток: 12
Резерв: 5
Доступно: 7

Если сайт отображает 12, а продать можно только 7, технически синхронизация работает идеально, но бизнес-результат неверный.

Очень многие интеграционные ошибки возникают не в HTTP или JSON.

Они возникают из-за того, что две системы по-разному понимают смысл одного и того же поля.


Поэтому перед API-контрактом нужен семантический контракт

Обычный API-контракт говорит:

{
  "stock": 7
}

Семантический контракт отвечает:

Что именно означает stock?

Например:

Количество единиц номенклатуры, доступных для оформления нового заказа на основном складе после вычета действующих резервов.

Теперь разные разработчики трактуют поле одинаково.

Для сложных интеграций это чрезвычайно полезная документация.


Цена тоже может быть сложнее одного числа

Что передавать на сайт?

Розничную цену?

Персональную?

Цену конкретного договора?

С НДС?

Без НДС?

Цена действует сейчас или со следующего дня?

В какой валюте?

Что делать, если в момент оформления заказа цена изменилась?

Поэтому заказ должен сохранять не только ID товара и количество, но и коммерческие условия, подтверждённые пользователю в момент покупки.

Иначе изменение справочника цен потенциально изменит смысл уже созданного заказа.


Заказ нужно рассматривать как отдельный жизненный цикл

Например:

DRAFT
  ↓
PLACED
  ↓
ACCEPTED
  ↓
PAID
  ↓
PROCESSING
  ↓
SHIPPED
  ↓
COMPLETED

На каждом этапе владельцем статуса может быть разная система.

Например:

PLACED      → сайт
ACCEPTED    → 1С
PAID        → платёжная система
PROCESSING  → 1С
SHIPPED     → склад / 1С
COMPLETED   → 1С

Интеграционный слой должен понимать эту ответственность.

Иначе CRM может сообщить одно состояние, 1С другое, а клиентский кабинет показать третье.


CRM-интеграция имеет свои типичные проблемы

Представим сайт компании, которая получает заявки на разработку.

Пользователь заполняет форму.

В CRM должны появиться:

Контакт
Компания
Сделка
Источник
Комментарий
Файлы
UTM-данные

Главный вопрос:

что делать с повторной заявкой?

Если каждый раз создавать нового клиента, CRM быстро заполнится дублями.

Чаще логика может выглядеть так:

Получена заявка
      ↓
Ищем контакт
      ↓
Существует?
  ↙           ↘
да             нет
↓               ↓
используем      создаём
существующий    контакт
      \         /
       \       /
        ↓     ↓
      новая сделка

Но правило идентификации должно соответствовать конкретному бизнесу.

Иногда один email однозначно определяет человека.

Иногда нет.


Не все данные нужно передавать сразу

Есть ещё один полезный вопрос:

нужна ли этой системе конкретная информация вообще?

Например, CRM может не требоваться вся техническая структура загруженного файла.

Достаточно:

filename
size
secure_url

А сам файл продолжает храниться в объектном хранилище приложения.

Это уменьшает дублирование данных и количество связей между системами.

Интеграция не обязана копировать всё из одной базы в другую.

Она должна передавать ровно те данные, которые необходимы для процесса.


Универсальный API — третья категория интеграций

Кроме 1С и CRM сайт может работать с:

платёжным провайдером;

службой доставки;

телефонией;

email-платформой;

SMS;

Telegram;

геокодированием;

электронной подписью;

банком;

AI-сервисом;

системой аналитики.

Технически многие из них выглядят одинаково:

Наш backend
    ↓ HTTPS
External API

Но поведение у всех разное.

Один API поддерживает webhook.

Другой приходится опрашивать.

У одного лимит — тысячи запросов в минуту.

Другой разрешает десять.

Один гарантирует стабильные ID.

У другого объект можно удалить без уведомления.

Поэтому «подключить пять API» невозможно качественно оценить как одну типовую задачу.


Polling и webhook решают похожую задачу по-разному

Представим, что нужно узнавать статус доставки.

Polling

Наш сервер периодически спрашивает:

Статус изменился?
Статус изменился?
Статус изменился?

Преимущества — простая модель и контроль со своей стороны.

Недостаток — лишние запросы и задержка между фактическим изменением и следующим опросом.

Webhook

Сервис доставки сам сообщает:

Заказ 1842 передан курьеру

Это быстрее и экономичнее по запросам, но требует публичного endpoint, проверки подлинности сообщения, обработки повторов и восстановления после пропущенных событий.

В некоторых интеграциях разумно сочетать оба механизма:

webhook для оперативности;

periodic reconciliation для контроля состояния.


Rate limit нужно учитывать до нагрузки

Внешний API может разрешать, например, ограниченное количество запросов за минуту.

Пока клиентов десять, это незаметно.

Когда их становится тысяча, приложение начинает получать:

429 Too Many Requests

Если каждый пользовательский запрос напрямую вызывает внешний API, проблема мгновенно попадает в интерфейс.

Лучше заранее понимать, какие данные можно кэшировать, какие операции отправлять в очередь, а какие действительно должны выполняться синхронно.

Интеграционная архитектура часто определяется не только нашими возможностями, но и ограничениями чужой системы.


Timeout — это не ошибка бизнес-операции

Это важное различие.

Наш backend отправил запрос.

Через 15 секунд истёк timeout.

Что мы знаем?

Только то, что не получили ответ.

Мы не знаем, выполнил ли внешний сервис операцию.

Возможно:

Запрос
  ↓
External API
  ↓
Операция выполнена
  ↓
Ответ
  X соединение оборвалось

Поэтому нельзя автоматически трактовать timeout как:

Операция не произошла.

Именно здесь снова нужны external ID и идемпотентность.


Ошибки тоже нужно классифицировать

Ответ:

500 Internal Server Error

и:

400 Invalid customer data

требуют разной реакции.

При временном 500 повторная попытка может быть разумной.

Если внешний сервис говорит:

ИНН имеет неверный формат,

повторять тот же запрос каждые пять минут бессмысленно.

Интеграционный слой должен различать:

Temporary error
Permanent validation error
Authentication error
Rate limit
Conflict
Unknown failure

Тогда временная проблема отправляется на retry.

Ошибка данных требует вмешательства пользователя или администратора.

Ошибка авторизации создаёт техническое оповещение.

Так система перестаёт бесконечно повторять заведомо невозможную операцию.


Retry тоже может положить систему

Представим, внешний сервис перестал работать.

10 000 задач получают ошибку.

Через одну минуту все 10 000 одновременно повторяются.

Внешняя система только восстановилась — и сразу получает лавину запросов.

Это называется одним из вариантов thundering herd.

Поэтому реальные retry-механизмы часто используют увеличивающуюся задержку:

1 минута
5 минут
15 минут
1 час

и небольшое случайное смещение времени.

Так восстановление получается плавнее.


Нужен ли отдельный интеграционный сервис

Не всегда.

Для небольшого приложения вполне нормально иметь:

Backend
│
├── Projects
├── Users
├── Billing
└── Integrations
    ├── 1C
    ├── CRM
    └── Telegram

То есть интеграции существуют модулями внутри основного backend.

Отдельный сервис становится оправданнее, если обменов много, они имеют собственную нагрузку, сложные очереди и независимый жизненный цикл.

Например:

Application
     ↓
Event bus
     ↓
Integration service
  ├── 1C adapter
  ├── CRM adapter
  ├── Payment adapter
  └── Delivery adapter

Но выносить интеграции в микросервис только ради красивой архитектурной схемы нет необходимости.

Сложность должна решать реальную проблему.


Adapter помогает не размазывать чужой API по всему приложению

Представим, CRM используется напрямую:

project.service
→ CRM API

user.service
→ CRM API

admin.service
→ CRM API

notification.service
→ CRM API

Теперь контракт CRM находится во всём коде.

Лучше создать границу:

Application
      ↓
CRM Adapter
      ↓
External CRM API

Приложение вызывает:

createDeal()
updateCustomer()
addComment()

А adapter знает, как конкретная CRM называет поля, endpoints и статусы.

Если CRM изменит API или компания однажды перейдёт на другой продукт, область изменений становится значительно меньше.


То же полезно делать с 1С

Основной backend не обязательно должен знать, что внутри 1С существует определённый справочник или регистр.

Он работает с собственным контрактом:

createOrder()
getAvailableStock()
updateCustomer()

Конкретное преобразование выполняется интеграционным модулем.

Получается:

Domain
  ↓
Integration interface
  ↓
1C Adapter
  ↓
1С

Это снижает связанность систем и делает тестирование проще.


API-контракт должен иметь версию

Сегодня endpoint возвращает:

{
  "price": 1200
}

Через год появляется:

{
  "price": {
    "amount": 1200,
    "currency": "RUB"
  }
}

Если старое приложение ожидает число, оно перестаёт работать.

Поэтому для внешних контрактов полезно заранее иметь стратегию изменения.

Например:

/api/v1/orders
/api/v2/orders

Это не значит, что каждое изменение требует новой версии API.

Добавление необязательного поля обычно можно сделать обратно совместимо.

Но breaking changes нужно контролировать.

Особенно если интеграцию используют системы, которые обновляются разными командами.


Полезно документировать не только endpoint

Хорошая документация отвечает не только:

POST /orders

Она объясняет:

что означает операция;

какие поля обязательны;

кто является источником данных;

какие значения допустимы;

можно ли повторять запрос;

какие ошибки возвращаются;

какие состояния объекта существуют;

какие переходы допустимы;

как долго может выполняться операция;

что происходит при недоступности системы.

Для сложной интеграции этот документ иногда ценнее десятков страниц обычного технического задания.


Не стоит использовать production как первый интеграционный стенд

Желательно иметь отдельную среду.

Например:

WebRuta staging
        ↕
1C test base

а не сразу:

WebRuta staging
        ↕
боeвая бухгалтерия

Особенно когда интеграция умеет создавать документы или изменять финансовые данные.

Хорошо, если внешняя система предоставляет sandbox.

Если нет, можно использовать отдельную тестовую информационную базу, тестовую организацию или специально ограниченный контур — в зависимости от инфраструктуры конкретного проекта.


Тест «успешно передали заказ» недостаточен

Нужно проверять не только happy path.

Например, что произойдёт, если:

1С недоступна;

API вернул 500;

API вернул 429;

ответ задержался на 30 секунд;

один webhook пришёл дважды;

два события пришли в обратном порядке;

товар отсутствует;

клиент уже существует;

заказ уже был создан;

доступ к API отозван;

в запросе отсутствует обязательное поле.

Именно здесь обнаруживается большая часть архитектурных проблем интеграции.


Полезный тест: выключить внешнюю систему

Перед запуском можно буквально проверить сценарий:

CRM недоступна час. Что происходит с сайтом?

Если перестала работать отправка заявок пользователями — системы связаны слишком жёстко.

Другой тест:

1С недоступна два часа. Что происходит с заказами?

В зависимости от бизнеса правильным поведением может быть разное.

Но оно должно быть заранее определено.

Например, сайт продолжает принимать заказы, помещает их в очередь и показывает администратору задержку синхронизации.

Или, если точность остатка критична, временно ограничивает определённые операции.

Главное — не оставлять это случайному поведению exception.


Интеграции необходимо наблюдать после запуска

Пользователю мало пользы от того, что интеграция «технически включена».

Администратор должен понимать её состояние.

Например:

ИнтеграцияСостояниеПоследний успешный обменОшибки
1СРаботает14:320
CRMРаботает14:332
TelegramОшибка13:5817

Открыв ошибку CRM:

Заявка #4812

Ошибка:
CONTACT_VALIDATION_FAILED

Причина:
Некорректный номер телефона

[Исправить]
[Повторить]

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


Integration log и audit log — разные вещи

Integration log отвечает:

Что происходило при обмене между системами?

Например:

14:03:12
CRM createDeal
request: ...
response: 201
duration: 420 ms

Audit log отвечает:

Кто совершил бизнес-действие?

Например:

14:02:58
Пользователь Иван Петров
подтвердил заказ #1842

Эти журналы решают разные задачи.

Первый нужен разработчикам и поддержке.

Второй — для истории значимых действий.


Не стоит бездумно логировать все данные

Если в запросах передаются:

пароли;

access token;

cookie;

персональные данные;

платёжная информация;

секретные ключи,

они не должны автоматически попадать в обычный application log.

Например, вместо полного:

Authorization: Bearer eyJ...

можно вообще не записывать заголовок авторизации.

Чувствительные поля payload могут маскироваться.

Интеграционные логи сами являются данными, которые нужно защищать.


Отдельная учётная запись интеграции лучше человеческой

Плохой вариант:

Давайте подключим 1С под логином главного бухгалтера.

Или:

Возьмём API-token владельца CRM.

Теперь сменился сотрудник, пароль изменили — интеграция перестала работать.

Кроме того, внешний сайт потенциально получает права, которые ему вообще не нужны.

Предпочтительнее отдельный технический аккаунт с минимально необходимыми разрешениями.

Принцип простой:

интеграция должна иметь только те права, которые нужны для её функций.

Если она только читает каталог, ей не нужен административный доступ ко всей системе.


Секреты не должны находиться во frontend

API token 1С, CRM или стороннего сервиса нельзя безопасно спрятать внутри JavaScript-приложения, которое загружается браузером пользователя.

Правильная граница обычно выглядит так:

Browser
   ↓
Наш backend
   ↓
External API

Секрет хранится на серверной стороне.

Браузер его вообще не получает.

Исключения существуют для специально предназначенных публичных client-side API, но секретный server token к ним не относится.


Если 1С находится внутри локальной сети, архитектура меняется

Иногда 1С работает внутри компании и не доступна из интернета.

Просто открыть её наружу ради сайта — далеко не единственный вариант.

В зависимости от требований может использоваться контролируемый сетевой доступ, VPN, промежуточный integration gateway или исходящее соединение со стороны внутренней инфраструктуры.

Например:

Internet
   │
Web backend
   ↑
   │ HTTPS outbound
   │
Integration Agent
   │
   ↓
  1С

Так внутренняя база не обязана принимать произвольные входящие соединения из интернета.

Конкретная схема определяется инфраструктурой и требованиями безопасности.


Файловый обмен всё ещё существует — и иногда это нормально

Не каждая интеграция обязана быть real-time REST API.

В некоторых бизнес-процессах вполне допустима периодическая передача файла:

XML / JSON / CSV

Например:

Каждую ночь
1С формирует выгрузку
        ↓
SFTP / Object Storage
        ↓
Система импортирует изменения

Если данные обновляются раз в сутки и бизнесу этого достаточно, сложная событийная архитектура может быть лишней.

Качество решения определяется не модностью технологии, а тем, насколько она соответствует процессу.


Иногда real-time даже вреден

Представим отчётную систему, которой достаточно получать бухгалтерские данные раз в день.

Если построить real-time двустороннюю синхронизацию, появятся:

очереди;

event delivery;

повторы;

конфликты;

дополнительное мониторирование.

При этом бизнес-ценность останется той же.

Поэтому перед требованием:

Всё должно обновляться мгновенно

полезно спросить:

какую проблему создаст задержка в пять минут?

Если ответ — «никакую», архитектуру можно существенно упростить.


Интеграция должна иметь понятный SLA внутри проекта

Не обязательно формальный юридический SLA.

Но команде нужно понимать ожидаемое поведение.

Например:

Новые заявки:
передача в CRM — до 1 минуты.

Заказы:
передача в 1С — до 2 минут.

Каталог:
изменения — до 10 минут.

Полная сверка каталога:
раз в сутки.

Теперь архитектуру можно строить под конкретные требования.

Одни данные требуют очереди с высоким приоритетом.

Другие спокойно обрабатываются пакетами.


Данные иногда нужно преобразовать, а не просто передать

Сайт хранит:

country = "RU"

CRM:

country = "Россия"

1С:

country_ref = <UUID>

Это один и тот же смысл в трёх разных представлениях.

Появляется mapping layer:

RU
 ↓
Россия
 ↓
UUID: ...

Аналогично работают статусы:

Website:
IN_PROGRESS

CRM:
WORK

1С:
ВПроизводстве

Без централизованного mapping преобразования быстро оказываются разбросаны по коду.


Справочники требуют отдельного внимания

Если две системы используют список:

Москва
Санкт-Петербург
Казань
...

нужно решить, синхронизируется ли сам справочник или только его ID.

Ситуация усложняется, если один пользователь пишет:

Санкт-Петербург

а другая система содержит:

г. Санкт-Петербург

Совпадение по тексту становится ненадёжным.

Поэтому интеграционные ID и явные таблицы соответствия часто оказываются важнее, чем кажется на раннем этапе.


Удаление данных — одно из самых неоднозначных событий

Объект удалён на сайте.

Что делать в CRM?

Удалить?

Архивировать?

Оставить?

Пометить неактивным?

А если это уже бухгалтерский документ в 1С?

Физическое удаление может вообще быть недопустимо с точки зрения бизнес-процесса.

Поэтому событие:

DELETE

не всегда должно распространяться на соседнюю систему как физическое удаление.

Иногда правильное преобразование:

Website deleted
      ↓
CRM archived
      ↓
1С unchanged

Это ещё один пример, почему интеграция работает с бизнес-смыслом, а не только с CRUD-операциями.


Что делать, если формат данных поменялся

Представим, раньше система отправляла:

{
  "name": "Иван Петров"
}

Теперь нужны отдельные поля:

{
  "firstName": "Иван",
  "lastName": "Петров"
}

Старая версия интеграции ещё работает на production.

Новая уже развёртывается.

Если изменить контракт мгновенно, одна из сторон может перестать понимать другую.

Поэтому сложные интеграции требуют совместимых изменений:

сначала получатель начинает понимать старый и новый формат;

затем отправитель переходит на новый;

после стабилизации поддержка старого формата удаляется.

Это особенно важно, когда две системы обновляют разные команды.


Почему интеграцию трудно оценить одной цифрой

Сравним три задачи.

Вариант A

Отправить заявку в CRM через готовый endpoint.

Сайт → CRM

Одно направление, небольшой payload, без обратной синхронизации.

Вариант B

Синхронизировать с 1С каталог, цены, остатки и заказы.

Сайт ↔ 1С

Несколько сущностей, mapping, очередь, журнал, обработка ошибок.

Вариант C

Двусторонне синхронизировать клиентов, сделки, документы и статусы между сайтом, CRM и 1С.

       CRM
      ↗   ↘
   Сайт ↔ 1С

Есть конфликты, несколько источников истины, разные жизненные циклы и необходимость восстановления после частичного сбоя.

Во всех трёх случаях заказчик может сказать:

«Нужна интеграция».

Трудоёмкость при этом отличается в разы.


Что действительно влияет на стоимость

Полезнее считать не количество API, а свойства обмена.

Например:

ХарактеристикаПрощеСложнее
НаправлениеОдностороннееДвустороннее
ЧастотаПакет раз в суткиПочти real-time
Сущности1–2Десятки
ПреобразованиеПрямоеСложный mapping
КонфликтыНевозможныТребуют разрешения
ОшибкиМожно повторить вручнуюНужен auto-retry
ОбъёмСотни записейМиллионы
Внешний APIСтабильныйОграниченный/нестабильный
ДанныеНекритичныеФинансовые/операционные

Именно такие параметры позволяют получить нормальную оценку.


Что нужно выяснить до начала разработки

Разговор об интеграции становится значительно продуктивнее, если вместо:

Нам нужно связать сайт и 1С.

есть описание вроде:

Номенклатура, цены и остатки ведутся в 1С и передаются на сайт. Сайт не редактирует эти данные. Новый заказ сначала сохраняется в базе сайта, а затем передаётся в 1С. Временная недоступность 1С не должна мешать оформлению заказа. После успешного создания документа в 1С сайт хранит его внешний идентификатор. Статусы исполнения передаются из 1С обратно на сайт. Требуемое время обычной синхронизации — до двух минут.

Это всего один абзац.

Но архитектурной информации в нём значительно больше, чем в десятке страниц с макетами.


Как может выглядеть итоговая архитектура

Для проекта средней сложности схема может оказаться такой:

┌────────────────┐
│   Web Client   │
└───────┬────────┘
        │
        ↓
┌────────────────┐
│    Backend     │
│                │
│ Domain Logic   │
└───────┬────────┘
        │
   ┌────┴─────┐
   ↓          ↓
Database     Queue
              │
              ↓
     ┌─────────────────┐
     │ Integration     │
     │ Workers         │
     └───┬────┬────┬───┘
         │    │    │
         ↓    ↓    ↓
        1С   CRM   API

В обратную сторону:

1С / CRM / API
       ↓
    Webhook
       ↓
 Integration Layer
       ↓
 Validation
       ↓
 Idempotency
       ↓
 Domain Logic
       ↓
    Database
       ↓
 WebSocket / Notification
       ↓
     Client

На такой схеме уже видно, почему интеграция — это намного больше, чем один fetch().


Что произойдёт, если внешний сервис полностью исчезнет

Хороший архитектурный вопрос:

Что будет, если завтра CRM API станет недоступен на шесть часов?

Ещё лучше:

А если провайдер изменит API и старая версия перестанет работать?

И совсем полезный:

Можем ли мы увидеть все операции, которые не были синхронизированы, и безопасно повторить их после восстановления?

Если ответы существуют, интеграция становится контролируемой.

Если нет — бизнес зависит от поведения внешней системы сильнее, чем кажется.


Нельзя гарантировать, что внешнее API никогда не изменится

Даже хорошо документированные сервисы развиваются.

Появляются новые версии.

Методы объявляются устаревшими.

Меняется авторизация.

Вводятся новые ограничения.

Поэтому внешняя интеграция является не одноразовой задачей:

«Подключили и забыли»

а зависимостью, которую нужно сопровождать.

Это необходимо учитывать и при оценке стоимости владения веб-продуктом.


Интеграция закончена не после первого успешного запроса

На тестовом стенде всё может выглядеть идеально:

POST
 ↓
200 OK

В production происходят другие вещи:

timeout
duplicate
out-of-order event
rate limit
invalid token
partial failure
network error
changed schema
stale data

Поэтому настоящий критерий готовности другой.

Система должна:

передавать данные;
не создавать дубли;
переживать временную недоступность;
восстанавливаться после ошибок;
показывать состояние обмена;
позволять диагностировать проблему;
не нарушать целостность бизнес-данных.

Только тогда можно говорить о действительно работающей интеграции.


Самый полезный принцип: проектировать отказ до успешного сценария

Звучит немного странно.

Обычно сначала спрашивают:

Как создать заказ в 1С?

Для production-системы полезно сразу задать второй вопрос:

Что произойдёт, если создать заказ в 1С не получится?

Затем третий:

А если заказ создастся, но мы не узнаем об этом?

И четвёртый:

А если ответ придёт дважды?

После этих вопросов архитектура становится намного реалистичнее.

Почти любой внешний сервис однажды будет недоступен.

Почти любое сетевое соединение однажды оборвётся в самый неудобный момент.

Это не исключительная авария.

Это нормальное состояние распределённой системы, к которому нужно подготовиться.


Вместо вывода

Интеграция сайта с 1С, CRM или внешним API — это не задача передачи JSON из точки A в точку B.

Настоящая интеграция отвечает на гораздо более сложные вопросы.

Какая система является источником истины?

Как идентифицировать один и тот же объект в разных системах?

Кто может изменять данные?

Что делать с конфликтами?

Можно ли безопасно повторить операцию?

Что произойдёт после timeout?

Как восстановить обмен после сбоя?

Как обнаружить рассинхронизацию?

Как администратор увидит ошибку?

Как изменять API без остановки соседних систем?

Когда эти вопросы решены, конкретный транспорт — REST, OData, HTTP-сервис, webhook или файловый обмен — становится уже инструментом реализации.

Поэтому хорошая интеграция редко выглядит впечатляюще для обычного пользователя.

Наоборот.

Пользователь оформляет заказ, отправляет заявку или смотрит статус и вообще не думает о том, что за интерфейсом одновременно работают сайт, CRM, 1С, платёжная система и несколько внешних сервисов.

И в этом, пожалуй, главный признак качественно спроектированного обмена:

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

Есть похожая задача?

Опишите продукт, интеграции и ограничения. До разработки зафиксируем объём, риски и критерии приёмки.

Обсудить проект →