Интеграция МойСклад и n8n: товары, остатки и заказы без дублей ¶
Проблема: МойСклад становится источником товаров, остатков и заказов, но простая синхронизация по расписанию быстро создаёт дубли контрагентов, скачущие остатки и конфликт между интернет-магазином, маркетплейсом и складом.
Решение: Решение — строить integration layer в n8n: нормализовать SKU и контрагентов, использовать внешний ключ заказа, проверять idempotency, обновлять только изменившиеся остатки и вести audit-log каждого write-действия в МойСклад.
Материал закрывает практический интент: интеграция МойСклад и n8n, JSON API МойСклад, синхронизация остатков, автоматизация склада, заказы без дублей, idempotency key. Внутри есть импортируемый workflow JSON, тестовый payload, Code Node, таблица архитектуры, Schema.org HowTo, LLM-разметка, визуальная схема и production-чеклист.
Проблема и решение: почему простой webhook ломает учёт ¶
В n8n легко собрать happy path: принять webhook, сделать HTTP Request и получить зелёный execution. Но backoffice-интеграции ломаются не на первом успешном запросе, а на повторной доставке, неполном payload, изменении схемы API, ручной правке в учётной системе и частичном сбое после write-операции.
Поэтому эта страница описывает не абстрактную связку сервисов, а готовую production-модель. Сначала фиксируется контракт данных, затем проверяются ключи, потом включается idempotency, и только после этого workflow меняет заказ, остаток, цену или документ. Такой подход снижает количество дублей, делает replay безопасным и помогает поддержке объяснить каждое изменение.
Архитектура workflow для интеграции ¶
| Нода | Роль | Что проверить |
|---|---|---|
| Webhook / Schedule | Принимает событие заказа или запускает сверку остатков | event_id, период, источник данных |
| Normalize order and SKU | Приводит SKU, телефон, ИНН и суммы к стабильному виду | SKU не пустой, qty > 0, сумма совпадает |
| Check idempotency | Ищет обработанный external_order_id или event_id | Postgres unique key или custom field |
| MoySklad API request | Создаёт/обновляет заказ, контрагента или остаток | JSON API 1.2, auth, timeout, retry |
| Audit and alert | Пишет результат и отправляет ошибки владельцу | payload_hash, status, href, reviewer |
Почему не стоит начинать с “синхронизировать всё”
У каждой системы есть зоны ответственности. Сначала автоматизируйте один поток: заказ, остаток, цена или уведомление. После стабильных тестов добавляйте второй поток и общую сверку. Иначе ошибки разных процессов смешаются в одном execution.
Контракт входных данных ¶
Контракт нужен для тестов, replay и передачи задачи между разработчиком, интегратором и владельцем процесса. Не завязывайтесь на текстовое название товара или клиента: используйте стабильные внешние ID, артикулы, event_id, timestamp и idempotency key.
{
"event_id": "ms-order-10492-updated",
"event_type": "customerorder.updated",
"source": "moysklad",
"external_order_id": "shop-10492",
"moysklad_href": "https://api.moysklad.ru/api/remap/1.2/entity/customerorder/uuid",
"customer": {
"name": "ООО Ромашка",
"inn": "7700000000",
"phone": "+7 916 123-45-67"
},
"items": [
{
"sku": "SKU-001",
"qty": 2,
"price": 1490
},
{
"sku": "SKU-002",
"qty": 1,
"price": 3900
}
],
"warehouse": "main-msk",
"payment_status": "paid",
"updated_at": "2026-05-30T10:00:00+03:00"
}
Нормализация и проверка данных в Code Node ¶
Code Node ниже не заменяет всю бизнес-логику, но задаёт безопасный вход: выбрасывает пустые обязательные поля, приводит идентификаторы к единому виду и формирует ключ идемпотентности. В production этот блок лучше покрыть отдельными тестовыми payload.
const src = $json.body ?? $json;
const orderId = String(src.external_order_id ?? src.id ?? '').trim();
if (!orderId) throw new Error('No external_order_id for MoySklad sync');
const items = Array.isArray(src.items) ? src.items : [];
if (!items.length) throw new Error(`Order ${orderId} has no items`);
const normalizedItems = items.map((item) => ({
sku: String(item.sku ?? item.article ?? '').trim().toUpperCase(),
quantity: Number(item.qty ?? item.quantity ?? 0),
price: Number(item.price ?? 0)
}));
for (const item of normalizedItems) {
if (!item.sku || item.quantity <= 0) throw new Error(`Invalid item in ${orderId}`);
}
return [{ json: {
idempotency_key: `moysklad:customerorder:${orderId}`,
external_order_id: orderId,
customer_phone: String(src.customer?.phone ?? '').replace(/[^0-9+]/g, ''),
customer_inn: String(src.customer?.inn ?? '').replace(/\D/g, ''),
items: normalizedItems,
warehouse: src.warehouse ?? 'main',
operation: 'upsert_customer_order',
audit: { event_id: src.event_id, received_at: new Date().toISOString() }
}}];
Готовый workflow JSON: скачать и импортировать ¶
Файл /assets/workflows/integration-moysklad-n8n-stock-order-sync.json можно импортировать в n8n как основу. После импорта замените credentials, endpoint, ID склада/магазина/инфобазы и правила mapping под ваш контур.
{
"name": "Nodbot - MoySklad stock and order sync with idempotency",
"nodes": [
{
"name": "Webhook / Schedule",
"purpose": "Принимает событие заказа или запускает сверку остатков"
},
{
"name": "Normalize order and SKU",
"purpose": "Приводит SKU, телефон, ИНН и суммы к стабильному виду"
},
{
"name": "Check idempotency",
"purpose": "Ищет обработанный external_order_id или event_id"
},
{
"name": "MoySklad API request",
"purpose": "Создаёт/обновляет заказ, контрагента или остаток"
},
{
"name": "Audit and alert",
"purpose": "Пишет результат и отправляет ошибки владельцу"
}
],
"connections": "Webhook → Normalize → Idempotency → API Request → Audit/Respond"
}
Пошаговая настройка ¶
- Создайте технического пользователя МойСклад с минимальными правами на заказы, товары и остатки.
- Опишите source of truth: кто главный по SKU, остаткам, ценам и статусам заказа.
- Импортируйте workflow JSON, замените credential и URL JSON API МойСклад.
- Запустите тестовый payload дважды и убедитесь, что второй запуск не создаёт дубль заказа.
- Включите audit-log, alert на 401/403/429/5xx и ручную очередь для неизвестных SKU.
Что проверить после импорта workflow
Откройте каждую ноду, замените credentials, включите dry-run там, где есть write-действия, и выполните тест на отдельной сущности. Не запускайте массовую синхронизацию до проверки дублей, лимитов API и rollback-плана.
Тесты перед production ¶
Минимальный smoke test:
curl -X POST "https://YOUR-N8N-DOMAIN/webhook/moysklad-stock-order-sync" \
-H "Content-Type: application/json" \
--data @integration-moysklad-n8n-stock-order-sync-payload.json- повторный заказ с тем же external_order_id
- неизвестный SKU
- отрицательный или нулевой остаток
- изменение цены больше допустимого процента
- ошибка API МойСклад 401/403/429
- частичный сбой после создания контрагента
Отдельно проверьте, что retry n8n не создаёт повторную запись. Для критичных действий используйте durable storage: Postgres, custom field, audit table или другой слой с уникальным ключом.
Production-риски ¶
- Двусторонняя синхронизация без source of truth заставляет остатки “прыгать”.
- Дедупликация по названию клиента создаёт дубли контрагентов при каждом новом написании.
- Массовое обновление остатков без diff перегружает API и усложняет расследование.
- Секреты JSON API попадают в публичный workflow или execution data.
- Нет ручной очереди для неизвестных SKU, поэтому workflow списывает не тот товар.
Полезные ссылки и смежные материалы ¶
Критерии готовности ¶
- Повторный external_order_id не создаёт второй заказ.
- Каждый SKU сопоставлен с кодом номенклатуры или отправлен в review.
- Остатки обновляются через diff и имеют safety stock.
- Ошибки API попадают в alert или DLQ с понятным сообщением.
- Есть владелец workflow, audit-log и инструкция replay.