Почему стандартные способы интеграции RetailCRM и 1С ломаются на практике
Типовой модуль обмена из коробки поддерживает конкретный список конфигураций: УТ 11.5+, УНФ 3.0, ERP 2.5. Если у вас что-то другое, скажем, доработанная УТ 10.3 или самописная конфигурация на базе БП, модуль либо не установится, либо установится и будет работать до первого серьёзного заказа. Дальше начинаются ошибки сериализации, битые ссылки на номенклатуру и тихий пропуск документов без каких-либо алертов.
Но даже на поддерживаемых конфигурациях есть системная проблема. И CommerceML, и прямой HTTP-обмен работают сеансами: данные приходят пакетом раз в несколько минут, в зависимости от настройки. При низком трафике это терпимо. При высоком трафике расхождения в остатках возникают из-за самого принципа пакетного обмена: резерв ставится в RetailCRM, но в 1С его ещё нет. Менеджер видит товар доступным, принимает ещё один заказ, и потом приходится разбираться вручную.
С 2026 года добавился ещё один источник поломок. Изменились ставки НДС и расширились требования к маркировке ряда товарных категорий. Старые интеграции, написанные под предыдущие правила, передают в поле vatRate значения, которые 1С больше не принимает как корректные. Документ либо не проводится, либо проводится с ошибкой, которую бухгалтер замечает не сразу. Это уже не техническая неприятность, это налоговый риск.
n8n закрывает все три проблемы за счёт позиции в архитектуре. Он стоит между RetailCRM и 1С как middleware: получает вебхук от RetailCRM в момент события, трансформирует payload под нужный формат, подставляет актуальные ставки НДС и маркировочные атрибуты, после чего отправляет запрос в 1С синхронно. Никаких сеансов, никакого ожидания следующего окна обмена. И логику трансформации можно менять в визуальном редакторе, не трогая конфигурацию 1С.
Похожие задачи возникают и в других CRM-системах: например, автоматизация заказов и управление складом в RetailCRM через n8n решается схожими принципами, но с другим набором сценариев.

Схема показывает, как n8n стоит посередине между RetailCRM и 1С, принимая вебхуки с одной стороны и отправляя запросы к API 1С с другой.
Архитектура потока данных: что и в какую сторону идёт
Перед тем как писать первый узел в n8n, нужно чётко ответить на вопрос: кто кому что говорит и при каком событии. Без этого получается каша, где один воркфлоу пытается делать всё, а при падении ломается интеграция целиком.
Я разделяю два независимых потока.
Поток первый: RetailCRM -> 1С. Это реакция на действия покупателя. Новый заказ, смена статуса, отмена. RetailCRM стреляет вебхуком на /api/v5/orders/create или /api/v5/orders/{externalId}/edit, n8n его ловит и передаёт данные в 1С. Здесь важна скорость: покупатель нажал "Оформить заказ" и ждёт подтверждения. Заметная задержка раздражает и снижает конверсию.
Поток второй: 1С -> RetailCRM. Движение в обратную сторону. Изменились остатки после отгрузки, склад провёл инвентаризацию, поставщик обновил прайс. Триггером служит либо регламентное задание в 1С по расписанию, либо HTTP-вызов из 1С напрямую в n8n при изменении документа. Второй вариант быстрее, но требует, чтобы 1С умела делать HTTP-запросы, что есть начиная с версии 8.3 через объект HTTPСоединение.
Резервы живут только в 1С. RetailCRM не хранит резерв самостоятельно: она отправляет запрос через n8n, 1С проверяет наличие, создаёт резерв в своей базе и возвращает подтверждение или отказ. Если отказ, n8n обязан записать это обратно в заказ CRM, иначе менеджер будет продавать товар, которого физически нет.
Остатки синхронизируются по расписанию. N8n с заданным интервалом делает GET-запрос к веб-сервису 1С, получает текущие остатки по складам и пушит результат в /api/v5/store/inventories/upload. Конкретный интервал подбирается под объём каталога и нагрузку на систему. Если нужна секундная точность, надо переходить на событийный триггер из 1С, но это усложняет конфигурирование на стороне 1С.
Каждый из этих потоков живёт в отдельном воркфлоу n8n. Это принципиальное решение. Когда воркфлоу синхронизации остатков падает из-за таймаута к 1С, воркфлоу приёма заказов продолжает работать. Монолитный воркфлоу "всё в одном" лишает тебя этой изоляции: один сломанный шаг блокирует весь поток, и ты ищешь проблему в 400 строках JSON-дампа.

Данные ходят в обе стороны: заказы и клиенты идут из RetailCRM в 1С, остатки и цены возвращаются обратно по расписанию.
Подготовка 1С: веб-сервис или HTTP-сервис для приёма запросов от n8n
Начну с версии. В 8.3.20 и выше HTTP-сервисы публикуются нативно, без сторонних адаптеров и без надстроек типа 1C:Enterprise REST. Если у вас именно эта версия или новее, забудьте про SOAP и про попытки эмулировать REST через внешние прокси. Всё уже есть в платформе.
Публикация через IIS выполняется в конфигураторе: заходите в «Администрирование -> Публикация на веб-сервере», указываете имя приложения и корневой каталог IIS, ставите галку напротив HTTP-сервисов. Apache работает аналогично, только вручную правите файл default.vrd. После публикации сервис доступен по адресу вида http://your-server/имя_публикации/hs/имя_сервиса/ (точный формат зависит от настроек конкретной публикации).
Что должен уметь минимальный HTTP-сервис для интеграции с n8n
Для базового сценария хватит двух маршрутов:
POST /orders, принять тело заказа в JSON, создать документ "Заказ покупателя", зарезервировать товар, вернуть номер и статусGET /stocks, отдать остатки по складам в JSON, желательно с фильтром по номенклатуре через query-параметры
Вот скелет обработчика для POST:
// Пример минимального HTTP-сервиса 1С (модуль обработчика)
Функция ОбработатьЗаказ(Запрос)
Данные = ПрочитатьJSON(Запрос.ПолучитьТелоКакСтроку());
// создать документ Заказ покупателя
// зарезервировать товар
Ответ = Новый HTTPСервисОтвет(200);
Ответ.УстановитьТелоИзСтроки(ЗаписатьJSON(РезультатРезерва));
Возврат Ответ;
КонецФункции
На практике к этому скелету нужно добавить три вещи: проверку обязательных полей в Данные, обёртку в транзакцию с откатом при ошибке, и нормальный возврат ошибки с кодом 400 или 500. Без этого n8n получит 200 OK даже когда заказ не создался.
Типовая или нетиповая конфигурация
Если конфигурация нетиповая или сильно доработана, не трогайте основную конфигурацию ради HTTP-сервиса. Напишите внешнюю обработку с HTTP-сервисом. При обновлении платформы или конфигурации она не ломается. Я видел несколько проектов, где HTTP-сервис зашивали прямо в основную конфигурацию УТ, а потом при обновлении на новый релиз интеграция падала на несколько часов. Внешняя обработка от этого страхует.
Аутентификация
Два рабочих варианта: Basic Auth и токен в заголовке Authorization: Bearer <token>.
Basic Auth проще настроить со стороны 1С (используется стандартный пользователь ИБ), но передаёт логин и пароль в base64, что приемлемо только по HTTPS. Токен в заголовке чуть гибче: в 1С проверяете Запрос.Заголовки["Authorization"] и сравниваете с захардкоженным... нет, стоп. Не захардкоживайте токен в модуле. Выносите его в константу ИБ или в регистр сведений.
Со стороны n8n оба варианта закрываются через Credentials. Добавляете "HTTP Header Auth" или "Basic Auth" один раз, ссылаетесь на этот credential во всех HTTP Request нодах, и пароль нигде в тексте воркфлоу не светится. n8n хранит credentials в зашифрованном виде.
Про версии в 2026 году
Проверьте свою редакцию до начала работ. В 2026 году список версий с активной поддержкой 1С сократился, и несколько минорных веток 8.3.x перешли в статус устаревших. Публикация HTTP-сервисов поддерживается начиная с платформы 8.3, но конкретная минорная версия влияет на доступность отдельных возможностей. Если ваша платформа попала в deprecated, вы не получите патчи безопасности для веб-сервера 1С. Это риск, особенно если сервис смотрит наружу.
Настройка n8n: воркфлоу приёма вебхука от RetailCRM и создания заказа в 1С
Покажу конкретную сборку воркфлоу, без теории. Берём реальную цепочку нод и разбираем каждую по делу.
Webhook-нода: точка входа
В n8n создаёшь Webhook-ноду с методом POST. URL получится примерно такой:
https://n8n.yourhost.ru/webhook/retailcrm-order
Этот URL идёт в RetailCRM: Настройки -> Интеграции -> Вебхуки -> Добавить. Выбираешь события orders.create и orders.update. Больше ничего там не трогай, RetailCRM сама упакует payload и пришлёт его при каждом изменении заказа.
В настройках Webhook-ноды включи "Respond immediately" - иначе RetailCRM будет ждать ответа всё время выполнения воркфлоу, и при долгой работе 1С она может решить, что вебхук упал.
Function-нода: маппинг в формат 1С
RetailCRM шлёт свою структуру, 1С ждёт свою. Между ними Function-нода:
// Function-нода: маппинг заказа RetailCRM -> формат 1С
const order = $input.first().json.order;
return [{
json: {
externalId: order.id,
customer: {
phone: order.phone,
name: `${order.firstName} ${order.lastName}`
},
items: order.items.map(i => ({
sku: i.offer.xmlId,
quantity: i.quantity,
price: i.initialPrice,
vatRate: i.vatRate ?? 'VAT20' // актуально для 2026
})),
warehouse: order.delivery?.address?.city === 'Москва' ? 'MSK-01' : 'SPB-01'
}
}];
Несколько комментариев по коду. i.offer.xmlId - это артикул товара в 1С, он должен быть заполнен в каталоге RetailCRM заранее, иначе получишь пустой sku и 1С не найдёт номенклатуру. Проверь это до запуска в продакшн.
По НДС: в 2026 году для бизнеса на УСН с доходом выше 60 млн рублей действуют ставки 5% или 7%, а не стандартные 20%. Если у тебя такой случай, vatRate нужно маппить явно из справочника, а не ставить 'VAT20' как дефолт. Добавь условие:
vatRate: i.vatRate === 'VAT20' && isUSN ? 'VAT5' : (i.vatRate ?? 'VAT20')
Флаг isUSN выноси в отдельную переменную окружения n8n, не хардкоди в код.
HTTP Request-нода: вызов веб-сервиса 1С
Нода отправляет POST к REST-сервису 1С. Таймаут ставь с запасом относительно типичного времени ответа твоей 1С под нагрузкой. Retry настрой так: 3 попытки, задержка 10 секунд между ними. В n8n это делается в разделе "Settings" ноды, там есть "Retry On Fail".
Заголовки запроса:
Content-Type: application/json
Authorization: Basic <base64(user:password)>
Учётку для веб-сервиса 1С создавай отдельную, с минимальными правами только на нужные документы. Не используй администраторскую.
Обратная запись в RetailCRM
После того как 1С вернула успешный ответ с номером документа, нужно записать этот номер обратно. Добавляй ещё одну HTTP Request-ноду:
POST https://yourshop.retailcrm.ru/api/v5/orders/{externalId}/edit
В теле передаёшь externalId из ответа 1С и статус резерва. Без этого шага ты не узнаешь из RetailCRM, создался ли заказ в 1С, и операторы будут работать вслепую.
Error-нода: алерты при сбоях
Соединяй каждую ноду с Error Workflow. В самом error-воркфлоу ставь HTTP Request к Telegram Bot API:
POST https://api.telegram.org/bot<TOKEN>/sendMessage
Тело:
{
"chat_id": "-100xxxxxxxxx",
"text": "Сбой воркфлоу: {{ $json.message }}\nЗаказ: {{ $json.execution.data.order.id }}"
}
Telegram лучше email в этом сценарии - алерт приходит за секунды, а не теряется в очереди почтового сервера. Группу для алертов делай закрытой, добавь туда только тех, кто реально будет реагировать.
Дополнительно пиши ошибки в отдельную таблицу (PostgreSQL или хотя бы Google Sheets), чтобы потом можно было поднять упавший заказ вручную. Telegram-алерт исчезнет, если его пропустят, а запись в базе останется.

Воркфлоу принимает вебхук от RetailCRM, нормализует структуру заказа и передаёт его в 1С через HTTP-запрос к веб-сервису.
Синхронизация остатков: расписание, дельта и защита от гонки данных
При больших каталогах полная выгрузка каждый цикл начинает жрать время: 1С долго формирует ответ, n8n долго его обрабатывает, и в итоге следующий запуск стартует раньше, чем закончился предыдущий. Это и есть гонка данных в самом примитивном виде. Порог, при котором это начинает проявляться, зависит от производительности сервера 1С и сетевых условий.
Переход на дельта-выгрузку решает проблему объёма. 1С отдаёт только записи, изменившиеся после последней метки времени. Метку сохраняешь в глобальной переменной n8n или в Redis после каждого успешного цикла. При следующем запуске берёшь её, передаёшь в запрос к 1С как параметр, и получаешь в ответе только дельту, а не весь реестр.
Данные об остатках бери из регистра накопления НаличиеТоваровНаСкладах. Не из документов, не из справочников. Документы отражают движение, а не сальдо. Актуальный остаток на складе живёт именно в регистре, и только там.
Расписание в n8n. Schedule Trigger с cron-выражением */10 * * * * запускает воркфлоу каждые 10 минут. Ничего сложного, но сам по себе триггер не защищает от наложения запусков.
Защита от гонки. Перед тем как лезть в 1С и RetailCRM, воркфлоу должен проверить: не выполняется ли уже другой его экземпляр? Два способа:
- Глобальная переменная в n8n. В начале воркфлоу Function-нода читает переменную
sync_running. Еслиtrue, выполнение прерывается. Еслиfalse, ставитtrue. В конце (и в ветке ошибок) сбрасывает обратно вfalse. - Redis-блокировка через Redis-ноду. Надёжнее, особенно если у тебя несколько воркеров. Команда
SET sync_lock 1 NX EX 600атомарно ставит блокировку на 10 минут.NXгарантирует, что запись произойдёт только если ключа ещё нет.
Второй способ предпочтительнее в продакшене. Глобальная переменная не атомарна и в теории может дать сбой при одновременном старте двух триггеров с минимальным разрывом.
Загрузка в RetailCRM. Endpoint принимает остатки через /api/v5/store/inventories/upload. Лимит на запрос: 250 позиций. Если SKU больше, весь массив нужно разбить на чанки и прогнать через Loop Over Items.
Вот Function-нода, которая делает эту разбивку:
// Разбивка массива остатков на чанки по 250
const stocks = $input.first().json.stocks;
const chunkSize = 250;
const chunks = [];
for (let i = 0; i < stocks.length; i += chunkSize) {
chunks.push({ json: { offers: stocks.slice(i, i + chunkSize) } });
}
return chunks;
Каждый элемент массива chunks уходит в Loop Over Items как отдельный элемент, и HTTP Request-нода отправляет каждый чанк в RetailCRM последовательно. Между запросами можно добавить Wait-ноду на 200-300 миллисекунд, если RetailCRM начинает отвечать ошибкой 429.
Один нюанс по структуре запроса: RetailCRM ожидает в теле поле offers, а не stocks. Убедись, что в HTTP Request стоит Content-Type application/x-www-form-urlencoded и тело сериализовано правильно. Ряд людей теряет час именно на этом, потому что API возвращает 200, но данные не обновляются.
Резервирование товара: синхронный запрос и обработка отказа
Резерв надо создавать в момент оформления заказа. Не при оплате, не при подтверждении менеджером. Именно в момент создания. Пока покупатель вводит карточные данные, тот же товар может купить кто-то ещё, и вы получите двойную продажу с последующим ручным разбором полётов.
Схема выглядит так. N8n ловит вебхук orders.create от RetailCRM, сразу отправляет POST-запрос в 1С на создание резерва, разбирает ответ и пишет результат обратно в CRM через кастомное поле или смену статуса. Весь цикл синхронный, без отложенных очередей. Если 1С не ответила за отведённое время, n8n бросает ошибку и тоже меняет статус заказа. Заказ без явного статуса резерва - это уже проблема.
Когда 1С возвращает fail
Нет смысла просто логировать ошибку и двигаться дальше. Если товара нет, n8n переводит заказ в статус "Ожидает поступления" и немедленно отправляет уведомление менеджеру, хоть в Telegram, хоть на почту. Менеджер должен видеть это сейчас, не завтра утром при открытии CRM.
Кстати, тут n8n удобен: один узел Switch по полю success, и дальше два независимых ветвления без дублирования кода.
Частичный резерв
1С умеет резервировать частично. Запросил 5 единиц, зарезервировала 3 - и считает это успехом. В ответе будет массив позиций с реальными зарезервированными количествами. N8n надо пройтись по нему в узле Code или через Loop Over Items и сравнить с тем, что пришло в исходном заказе.
Разницу записывай в CRM явно. Не просто флаг "частичный резерв", а конкретно: "Артикул XYZ-001, заказано 5, зарезервировано 3, дефицит 2". Менеджер открывает заказ и сразу видит, что делать дальше.
Снятие резерва при отмене
Это та часть, которую чаще всего забывают настроить. При отмене заказа резерв в 1С остаётся висеть. Через месяц склад показывает минус по остаткам, начинается разбор - откуда.
Механика простая. Вебхук orders.update с новым статусом из группы отменённых (обычно это два-три статуса, зависит от конфигурации CRM) -> n8n отправляет DELETE-запрос на /reserve/{orderId} в 1С. В n8n-узле фильтрации проверяй именно смену статуса, а не любое обновление заказа, иначе запрос полетит при каждом редактировании поля.
Список "отменяющих" статусов лучше вынести в отдельный параметр рабочего процесса и не хардкодить прямо в условие. Статусы в RetailCRM периодически добавляют, и чтобы не лезть в каждый узел, достаточно будет обновить одну переменную.

Если 1С возвращает ошибку нехватки товара, воркфлоу автоматически обновляет статус заказа в RetailCRM и уведомляет менеджера.
Маппинг статусов и справочников между RetailCRM и 1С
Самое частое место, где интеграция ломается молча: RetailCRM отправляет статус assembling, а 1С его не знает и просто игнорирует документ. Никакой ошибки, никакого алерта. Заказ завис.
Статусы не совпадают ни по коду, ни по смыслу. Вот реальная картина:
| RetailCRM | 1С (УТ 11) | Комментарий |
|---|---|---|
new |
Новый |
Прямое совпадение по смыслу |
assembling |
В работе |
В 1С нет стадии сборки отдельно |
delivery |
В работе |
Тот же статус 1С, другой смысл |
complete |
Закрыт |
|
cancel |
Отменён |
Проверяй, есть ли уже движение по складу |
assembling и delivery оба ложатся в "В работе". Это значит, что изменение статуса в 1С не даёт однозначного ответа, на каком этапе заказ в CRM. Если нужна обратная синхронизация, придётся хранить предыдущий RetailCRM-статус на своей стороне.
Где хранить маппинг. Не внутри кода трансформации. Если зашить его прямо в Function-ноду n8n или в скрипт, то при любом изменении нужен деплой, а изменения нужны постоянно: добавили новый тип доставки, подключили ещё один склад, перешли на другую конфигурацию 1С. Два рабочих варианта:
- Google Sheets: менеджер правит сам, без программиста. Читается через ноду Google Sheets при старте воркфлоу или кешируется раз в час. Кстати, при настройке [двустор





