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

Как происходит интеграция сайта с 1С, CRM и API: архитектура обмена данными без дублей и потерь

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

Проблемы начинаются в тот момент, когда система сталкивается с реальностью.

CRM временно недоступна. 1С отвечает 12 секунд вместо одной. Пользователь дважды нажимает кнопку оформления заказа. Один webhook приходит повторно. Второй приходит раньше первого. HTTP-запрос завершается по timeout, хотя удалённая система успела обработать операцию. Менеджер меняет телефон клиента в CRM одновременно с тем, как пользователь исправляет его в личном кабинете.

И тогда интеграция перестаёт быть задачей «соединить два API». Она становится задачей проектирования распределённой системы.

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

Сначала нужно определить не API, а владельца данных

Одна из самых частых ошибок при разработке интеграции — сразу переходить к документации API.

Есть сайт. Есть CRM. Есть 1С. Значит, нужно просто настроить обмен.

Но сначала необходимо ответить на более важный вопрос:

какая система является источником истины для каждого типа данных?

Например:

ДанныеОсновная система
карточка товара1С
остаток товара1С
базовая цена1С
пользователь сайтасайт
лидCRM
история общения с клиентомCRM
заказсайт или CRM — зависит от архитектуры
бухгалтерский документ1С
платёжплатёжная система
статус доставкислужба доставки
маркетинговые согласиясайт или CRM

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

Представим, что название организации клиента можно изменить и в CRM, и в личном кабинете.

В 12:00 менеджер изменил:

ООО Альфа

на:

ООО Альфа Трейд

А в 12:00:03 пользователь сохранил старое название из открытой ранее формы.

Если архитектура определяет синхронизацию как «последняя запись побеждает», CRM неожиданно получит старые данные.

Поэтому качественная интеграция начинается с определения ответственности систем, а уже затем — с выбора REST API, webhook, очереди или другого транспорта.

Типичная архитектура: сайт, CRM и 1С отвечают за разные задачи

Рассмотрим интернет-магазин или B2B-сервис.

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

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

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

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

Получается уже не линейная цепочка, а сеть:

                       ┌──────────────┐
                       │ Платёжный API│
                       └───────┬──────┘
                               │
                               ▼
┌──────────┐       ┌─────────────────────┐       ┌────────────┐
│   Сайт   │◄─────►│ Интеграционный слой│◄─────►│    CRM     │
└────┬─────┘       └─────────┬───────────┘       └────────────┘
     │                       │
     │                       ▼
     │                 ┌───────────┐
     └────────────────►│    1С     │
                       └───────────┘

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

Без него сайт постепенно начинает содержать код вроде:

sendToCRM()
sendTo1C()
createPayment()
updateDelivery()
notifyTelegram()
sendEmail()

А затем каждая из этих операций обзаводится собственными retry, timeout, авторизацией, логированием и обработкой ошибок.

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

Как сайт может взаимодействовать с 1С

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

В частности, платформа позволяет публиковать автоматический REST-интерфейс на основе OData, через который внешние приложения могут читать, создавать и изменять данные. Также можно создавать собственные HTTP-сервисы и самостоятельно определять маршруты, структуру запросов и формат ответов.

Это даёт два принципиально разных подхода.

В первом случае сайт работает с достаточно универсальным интерфейсом объектов 1С.

Например:

GET  /odata/.../Catalog_Товары
POST /odata/.../Document_ЗаказПокупателя

Во втором в 1С создаётся специализированный HTTP API:

GET  /api/v1/products/123
POST /api/v1/orders
POST /api/v1/orders/984/cancel

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

Причина проста: внешний сайт обычно не должен знать внутреннее устройство конфигурации 1С.

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

Специализированный API позволяет создать стабильный контракт.

Сайт знает:

{
  "order_id": "WR-10452",
  "customer": {
    "name": "ООО Альфа",
    "inn": "7700000000"
  },
  "items": [
    {
      "sku": "A-135",
      "quantity": 3,
      "price": 1490
    }
  ]
}

А каким образом этот объект превращается в документы и регистры внутри 1С — ответственность самой интеграции.

Платформа 1С также поддерживает JSON при работе с HTTP-интерфейсами, поэтому такой формат обмена является вполне естественным для современных веб-приложений.

Как происходит интеграция сайта с CRM

С CRM ситуация похожая.

Большинство современных CRM предоставляют REST API, webhooks или оба механизма.

REST API используется, когда одна система сама инициирует операцию:

сайт → CRM

Например:

создать контакт
создать лид
создать сделку
изменить стадию
добавить комментарий

Webhook работает в противоположном направлении:

CRM → сайт

Например, менеджер перевёл сделку в статус «Согласовано», после чего CRM отправила событие сайту.

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

                   REST API
        ┌────────────────────────►
┌───────┴─────┐              ┌──────────┐
│    Сайт     │              │   CRM    │
└───────▲─────┘              └────┬─────┘
        │                         │
        ◄─────────────────────────┘
                 Webhook

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

команда и событие — не одно и то же.

Команда:

Создай сделку.

Событие:

Сделка создана.

Если смешивать эти понятия, интеграция быстро становится трудно управляемой.

Разберём жизненный цикл одного заказа

Представим, что пользователь оформляет заказ №10452.

Сайт сначала сохраняет заказ в собственной базе:

order_id = 10452
status = created
integration_status = pending

Это важный момент.

Плохая архитектура выглядит так:

Пользователь нажал «Оформить»
        ↓
сайт вызывает CRM
        ↓
сайт вызывает 1С
        ↓
если всё успешно — сохраняет заказ

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

Гораздо устойчивее другой вариант:

Пользователь
     ↓
Сайт
     ↓
Локальная транзакция
     ├── сохранить заказ
     └── создать событие интеграции
                ↓
             очередь
          ┌─────┴─────┐
          ↓           ↓
         CRM          1С

Пользовательский запрос заканчивается после того, как приложение надёжно сохранило заказ.

Передача во внешние системы может выполняться отдельно.

Так недоступность CRM не превращается автоматически в недоступность интернет-магазина.

Зачем интеграции очередь

Для небольшого сайта допустимо сразу вызывать внешний API:

await crm.createDeal(order);

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

Если CRM отвечает 300 миллисекунд — всё хорошо.

Если CRM отвечает восемь секунд — пользователь ждёт восемь секунд.

Если CRM не отвечает — пользователь получает ошибку.

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

const order = await db.createOrder(data);

await queue.publish({
  type: "order.created",
  orderId: order.id
});

return order;

А отдельный worker выполняет интеграцию:

async function processOrderCreated(event) {
  const order = await getOrder(event.orderId);

  await sendOrderToCRM(order);
  await sendOrderTo1C(order);
}

Появляется буфер между системами.

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

Для небольшой системы вместо отдельного RabbitMQ или Kafka вовсе не обязательно сразу вводить сложную инфраструктуру. В качестве очереди иногда достаточно PostgreSQL и отдельной таблицы задач.

Например:

CREATE TABLE integration_jobs (
    id UUID PRIMARY KEY,
    type TEXT NOT NULL,
    payload JSONB NOT NULL,
    status TEXT NOT NULL,
    attempts INTEGER NOT NULL DEFAULT 0,
    next_attempt_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Главное здесь не конкретная технология.

Главное — разделить пользовательскую операцию и потенциально ненадёжное взаимодействие с внешней системой.

Timeout не означает, что операция не выполнилась

Это одна из самых неприятных особенностей API-интеграций.

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

POST /crm/deals

Сайт отправляет его в CRM.

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

Но ответ по сети не успевает вернуться за установленный timeout.

Для сайта результат выглядит так:

TimeoutError

Но в CRM сделка уже существует.

Worker считает операцию неудачной и повторяет запрос.

В результате появляется вторая сделка.

Поэтому нельзя строить retry по принципу:

try {
  await createDeal();
} catch {
  await createDeal();
}

Повторная отправка должна быть безопасной.

И здесь появляется идемпотентность.

Идемпотентность защищает систему от дублей

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

operation_id = order:create:10452

И передавать его вместе с запросом:

POST /api/orders
Idempotency-Key: order:create:10452

Получатель сохраняет обработанный ключ.

Первый запрос:

order:create:10452
→ заказ создан

Повторный запрос:

order:create:10452
→ операция уже выполнялась
→ вернуть прежний результат

Тогда повтор после timeout перестаёт создавать дубли.

Если внешнее API не поддерживает Idempotency-Key, аналогичный механизм иногда приходится реализовывать на уровне интеграционного слоя.

Например, хранить соответствие:

website_order_id = 10452
crm_deal_id = 83951
1c_document_id = ...

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

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

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

Распространённая ошибка — считать webhook гарантированно уникальным.

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

Поэтому обработчик вида:

app.post("/webhooks/crm", async (req, res) => {
  await updateOrder(req.body);
  res.sendStatus(200);
});

слишком наивен.

Более устойчивый подход:

app.post("/webhooks/crm", async (req, res) => {
  verifySignature(req);

  const event = req.body;

  const exists = await db.integrationEvents.find(event.id);

  if (exists) {
    return res.sendStatus(200);
  }

  await db.transaction(async tx => {
    await tx.integrationEvents.insert({
      id: event.id,
      payload: event
    });

    await tx.jobs.insert({
      type: "crm.webhook",
      payload: event
    });
  });

  res.sendStatus(200);
});

HTTP-обработчик выполняет минимум работы: проверяет запрос, сохраняет событие и быстро отвечает.

Тяжёлая бизнес-логика выполняется отдельно.

Это уменьшает риск, что webhook будет отправляться повторно просто потому, что обработчик слишком долго работал.

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

Такое тоже возможно.

Например:

12:00:05 — заказ оплачен
12:00:06 — заказ создан

С точки зрения бизнеса это невозможно.

С точки зрения распределённой сети — вполне.

Первое событие могло пройти более быстрым маршрутом, а второе задержалось в очереди.

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

Вместо:

order.status = event.status;

лучше иметь правила переходов:

created
   ↓
awaiting_payment
   ↓
paid
   ↓
processing
   ↓
shipped
   ↓
completed

И отдельно разрешённые исключения:

awaiting_payment → cancelled
paid → refund_pending
refund_pending → refunded

Тогда событие:

completed → created

не откатит заказ назад только потому, что пришло позже.

По сути здесь начинает работать state machine — конечный автомат состояний.

Что делать, если сайт и CRM изменили данные одновременно

Синхронизация становится ещё сложнее, когда обмен двусторонний.

Предположим:

сайт → CRM
CRM → сайт

Пользователь меняет телефон на сайте.

Сайт отправляет изменение CRM.

CRM сохраняет телефон и отправляет webhook:

contact.updated

Сайт получает его и снова обновляет пользователя.

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

сайт
 ↓
CRM
 ↓
сайт
 ↓
CRM
 ↓
...

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

Например:

{
  "customer_id": 7281,
  "phone": "+7...",
  "source": "website",
  "operation_id": "01J..."
}

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

source = website

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

Для более сложных систем дополнительно используют версии:

{
  "customer_id": 7281,
  "version": 17
}

Если уже сохранена версия 18, событие версии 17 считается устаревшим.

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

Иногда в требованиях появляется фраза:

Все данные между сайтом и 1С должны синхронизироваться в реальном времени.

Звучит хорошо, но далеко не всегда действительно необходимо.

Статус оплаты желательно получить почти мгновенно.

Изменение заказа — тоже.

Но каталог из 150 000 товаров совсем не обязательно каждый раз запрашивать непосредственно из 1С при открытии страницы пользователем.

Схема:

Пользователь → сайт → 1С → сайт → пользователь

создаёт сильную связанность.

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

Гораздо практичнее:

1С
 ↓
синхронизация
 ↓
локальный каталог сайта
 ↓
пользователь

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

Например, полная синхронизация выполняется ночью, а изменения цен и остатков передаются инкрементально в течение дня.

Полная и инкрементальная синхронизация

Полная синхронизация означает:

выгрузить все товары
сравнить все товары
обновить всё состояние

При небольшом каталоге это допустимо.

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

Поэтому обычно появляется инкрементальная синхронизация:

дай все объекты, изменённые после 14:35:00

или:

дай события начиная с cursor=839245

Например:

GET /products/changes?after=2026-10-04T14:35:00Z

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

Надёжнее использовать последовательный cursor или версию:

10091
10092
10093
10094

Клиент запоминает:

last_cursor = 10094

и при следующем обмене запрашивает:

cursor > 10094

Так значительно проще понять, что ничего не пропущено.

Сопоставление данных — отдельная часть интеграции

Сайт может называть сущность:

customer

CRM:

contact

1С:

Контрагент

Это вовсе не означает, что структуры этих объектов совпадают.

На сайте:

{
  "name": "Иван Петров",
  "email": "user@example.com"
}

В CRM:

{
  "first_name": "Иван",
  "last_name": "Петров",
  "email": [
    {
      "value": "user@example.com",
      "type": "WORK"
    }
  ]
}

Поэтому между системами обычно существует mapping layer:

WebsiteCustomer
        ↓
CustomerMapper
        ↓
CRMContact

Хорошая архитектура не позволяет форматам внешних API распространяться по всему приложению.

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

const email =
  crmContact.custom_fields_values[4].values[0].value;

и такой код находится в двадцати файлах.

Лучше:

const customer = crmMapper.toCustomer(crmContact);

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

Интеграционный слой защищает бизнес-логику

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

                    ┌── CRM Adapter
                    │
Business Logic ─────┼── 1C Adapter
                    │
                    ├── Payment Adapter
                    │
                    └── Delivery Adapter

Бизнес-логика говорит:

await crm.createDeal(order);

а не:

await axios.post(
  `${config.crmHost}/rest/...`,
  createVerySpecificPayload(...)
);

То же относится к 1С.

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

Order
Customer
Product
Invoice
Payment

а не внутренними HTTP-маршрутами конкретного поставщика.

Это становится особенно полезно, когда через два года компания меняет CRM.

Если интеграция изолирована, меняется один адаптер.

Если API CRM проникло во всю кодовую базу — фактически приходится переписывать продукт.

Retry должен быть управляемым

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

Но бесконечный цикл:

ошибка
↓
повтор
↓
ошибка
↓
повтор

может окончательно положить восстанавливающийся сервис.

Поэтому применяется задержка между попытками.

Например:

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

Это называется exponential backoff.

Если система вернула:

400 Bad Request

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

При:

429 Too Many Requests

необходимо учитывать ограничение API.

При:

502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout

повтор, как правило, вполне разумен.

Поэтому retry — не просто attempts++.

У системы должна быть классификация ошибок.

Нужна очередь «неразобранных» событий

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

Удалять его нельзя.

Бесконечно повторять тоже нельзя.

Для этого используется Dead Letter Queue, или логически аналогичный механизм.

Получается:

обычная очередь
     ↓
несколько попыток
     ↓
не удалось обработать
     ↓
Dead Letter Queue

Администратор может увидеть:

order.created
order_id: 10452
attempts: 10
error: CRM returned 422

Исправить причину и запустить обработку повторно.

Без такого механизма проблемы интеграции часто обнаруживаются иначе:

Почему заказ клиента от прошлой среды так и не появился в 1С?

После чего разработчик начинает искать его среди гигабайтов логов.

Логи должны отвечать на бизнес-вопрос

Обычный лог:

Request failed with status 500

почти бесполезен.

Хороший лог:

{
  "timestamp": "2026-10-04T10:18:41Z",
  "service": "integration-worker",
  "operation": "crm.create_deal",
  "order_id": "10452",
  "correlation_id": "01K...",
  "attempt": 3,
  "duration_ms": 1842,
  "status": "failed",
  "http_status": 503
}

Теперь можно ответить на вопрос:

Что происходило с заказом 10452?

По correlation_id можно проследить весь путь:

сайт
 ↓
order.created
 ↓
CRM
 ↓
1С
 ↓
платёж
 ↓
доставка

Это называется distributed tracing, хотя даже без полноценной tracing-платформы единый correlation ID значительно упрощает поддержку.

Мониторить нужно не только ошибки

Интеграция может работать без ошибок и при этом быть неисправной.

Например:

очередь: 38 426 событий
ошибок: 0

Worker работает.

Но обрабатывает 10 событий в минуту, а приходит 100.

Формально всё «зелёное».

Фактически задержка синхронизации уже составляет несколько часов.

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

размер очереди
возраст самого старого события
среднее время обработки
процент успешных операций
количество retry
число событий в DLQ
время ответа 1С
время ответа CRM

Особенно показательный показатель:

oldest_pending_event_age

Если самому старому необработанному заказу 43 секунды — система, скорее всего, работает нормально.

Если ему 6 часов — проблема очевидна даже при нулевом количестве HTTP 500.

Outbox решает ещё одну неприятную проблему

Рассмотрим код:

await db.createOrder(order);
await queue.publish("order.created", order.id);

Между этими двумя строками процесс может аварийно завершиться.

Тогда заказ сохранён:

orders: есть

а событие:

queue: отсутствует

CRM никогда о нём не узнает.

Если поменять операции местами:

await queue.publish(...);
await db.createOrder(...);

возникает обратная ситуация: событие отправлено, а заказ сохранить не удалось.

Один из способов решения — Transactional Outbox.

В той же транзакции базы данных сохраняются и заказ, и событие:

BEGIN

INSERT INTO orders ...
INSERT INTO outbox_events ...

COMMIT

Отдельный publisher читает outbox_events и отправляет записи в очередь.

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

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

Exactly once обычно превращается в at least once

В интеграционных системах часто хочется гарантировать:

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

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

Поэтому архитектура обычно строится вокруг другой модели:

at least once delivery
+
idempotent processing

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

Но его повторная обработка безопасна.

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

Безопасность интеграции

Интеграционный API часто имеет значительно больше полномочий, чем обычный пользователь сайта.

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

const API_KEY = "...";

Всё взаимодействие с 1С, CRM и внутренними сервисами должно выполняться сервером.

Секреты хранятся в защищённой конфигурации окружения или специализированном хранилище секретов.

Для webhook желательно проверять подпись запроса.

Например:

X-Signature: ...

Сервер самостоятельно вычисляет HMAC от тела запроса и сравнивает результат.

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

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

интеграция должна получать минимально необходимые полномочия.

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

Версионирование API тоже необходимо продумать заранее

Сегодня сайт отправляет:

{
  "name": "Иван",
  "phone": "+7..."
}

Через два года потребуется:

{
  "name": {
    "first": "Иван",
    "last": "Петров"
  },
  "phones": [
    {
      "value": "+7...",
      "type": "mobile"
    }
  ]
}

Нельзя просто поменять контракт и надеяться, что все подключённые системы обновятся одновременно.

Поэтому публичный или межсервисный API желательно проектировать с учётом совместимости.

Например:

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

или использовать эволюцию схемы без разрушения старых полей.

Особенно опасны незаметные изменения смысла поля.

Если вчера:

price = цена за единицу

а завтра:

price = стоимость всей позиции

формально JSON остаётся валидным.

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

Как тестировать интеграцию

Проверки вида:

нажал кнопку → запись появилась в CRM

недостаточно.

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

Что произойдёт, если CRM недоступна 20 минут?

Если запрос завершился timeout?

Если webhook пришёл три раза?

Если 1С вернула некорректный JSON?

Если события поменялись местами?

Если worker упал после выполнения операции, но до подтверждения сообщения?

Если товар удалён в 1С?

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

Если API начал возвращать 429?

Если access token истёк?

Если была развёрнута новая версия сайта во время обработки очереди?

Именно такие сценарии определяют качество интеграции.

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

Контрактные тесты защищают от неожиданного изменения API

Интеграция может сломаться даже без единого изменения на сайте.

Например, сторонняя система изменила:

{
  "id": 128
}

на:

{
  "id": "128"
}

или перестала возвращать необязательное поле, которое разработчики сайта фактически считали обязательным.

Поэтому полезны contract tests.

Они проверяют не весь бизнес-процесс, а соглашение между системами:

какие поля обязательны
какие типы данных допустимы
какие HTTP-коды возможны
какие события существуют
какие версии API поддерживаются

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

Нужна ли отдельная интеграционная платформа

Не всегда.

Для простого сайта с одной CRM вполне может быть достаточно:

backend
+
REST API
+
webhook
+
таблица integration_jobs

Строить Kafka-кластер только ради передачи пяти лидов в день бессмысленно.

Но если система соединяет:

сайт
CRM
1С
платёжный сервис
склад
службу доставки
email
SMS
аналитику
партнёрский API

выделение интеграционного слоя становится всё более оправданным.

Главный критерий — не количество модных технологий.

Главный критерий — можно ли ответить на четыре вопроса:

Где сейчас находится конкретный заказ?

Какая система владеет его текущим состоянием?

Что произойдёт, если одна из систем станет недоступна?

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

Если на эти вопросы нет точного ответа, интеграция уже начинает превращаться в набор неявных связей.

Как выглядит здоровая интеграция

Возьмём тот же заказ №10452.

Пользователь оформляет его на сайте.

Сайт открывает транзакцию и сохраняет:

order = 10452
status = created

Одновременно создаётся outbox-событие:

order.created

Транзакция завершается.

Publisher передаёт событие worker.

Worker создаёт сделку в CRM с идемпотентным идентификатором:

website-order-10452

CRM отвечает:

deal_id = 83951

Связь сохраняется:

order 10452 ↔ crm deal 83951

Следующий обработчик передаёт заказ в 1С.

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

Через несколько минут retry выполняется успешно.

1С возвращает идентификатор документа.

Система сохраняет:

order 10452
crm      83951
1c       65d8...

Когда оплата проходит, платёжный сервис отправляет webhook.

Webhook проверяется, сохраняется и передаётся в очередь.

Статус заказа становится:

paid

CRM и 1С получают соответствующие события.

Каждая операция имеет correlation ID.

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

Это уже не просто «связали сайт с CRM».

Это управляемая интеграционная архитектура.

Главная ошибка — считать интеграцию передачей JSON

Сам JSON обычно является самой простой частью задачи.

Сложность появляется в других вопросах:

кто владеет данными;
как определять дубли;
что делать при timeout;
как повторять операции;
как восстанавливать обмен после сбоя;
что делать с устаревшими событиями;
как изменять контракт API;
как найти потерянную операцию;
как синхронизировать несколько источников;
как не сделать внешний сервис единой точкой отказа.

Именно ответы на эти вопросы отличают демонстрационную интеграцию от production-системы.

Вместо заключения

Интеграция сайта с 1С, CRM или внешним API начинается не с HTTP-запроса.

Она начинается с проектирования границ систем.

Нужно определить владельцев данных, контракты API, направление синхронизации, правила разрешения конфликтов и допустимые состояния объектов.

После этого появляются технические механизмы: REST API, HTTP-сервисы, webhooks, очереди, retry, идемпотентность, Transactional Outbox, Dead Letter Queue, версионирование и мониторинг.

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

Но три свойства стоит закладывать почти всегда:

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

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

Timeout перестаёт автоматически означать повторное создание сущности.

Повторный webhook перестаёт создавать дубли.

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

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

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

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

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