Интеграция ЮKassa и CRM через n8n: оплата, чек и идемпотентность ¶
Обновлено: 2026-05-30
Импортируйте JSON в n8n, замените CRM endpoint, Postgres credential и правило поиска сделки по order_id.
- Проблема: почему платежный webhook ЮKassa нельзя обрабатывать как обычную заявку
- Архитектура workflow ЮKassa → n8n → CRM
- Контракт входных данных payment.succeeded
- Проверка платежа и idempotency key (Code Node)
- Готовый workflow JSON: скачать и импортировать
- Пошаговая настройка webhook ЮKassa, n8n и CRM
- Тесты перед production и проверка API ЮKassa
- Production-риски платежных интеграций
- Полезные ссылки и смежные workflow
- Критерии готовности
Проблема: платежные уведомления ЮKassa могут приходить повторно, а неуспешные статусы выглядят почти так же, как успешные. Если workflow просто принимает webhook и сразу меняет сделку в CRM, можно дважды начислить услугу, отправить два письма или закрыть не тот заказ.
Решение: обрабатывать webhook как финансовое событие: проверить event, status и paid, собрать idempotency key по payment_id, при необходимости сверить статус через API ЮKassa и только потом обновить сделку в CRM.
Статья закрывает сценарий “ЮKassa → n8n → CRM”: готовый workflow JSON, тестовый payload, Code Node, curl, таблица архитектуры, production-риски и чек-лист запуска.
Проблема: почему платежный webhook ЮKassa нельзя обрабатывать как обычную заявку ¶
Форма заявки может терпеть повтор, а платеж — нет. Для оплаты важно не только получить payment.succeeded, но и понять, какой заказ или сделку обновлять, что делать при повторном уведомлении и как доказать, что business action выполнен один раз.
ЮKassa ожидает подтверждение получения webhook, но ответ 200 OK не должен означать “мы уже закрыли сделку”. Он должен означать “мы приняли событие и обработали его по безопасному правилу”. Поэтому workflow лучше строить вокруг идемпотентности и журнала платежей.
Архитектура workflow ЮKassa → n8n → CRM ¶
| Нода | Роль | Что проверить |
|---|---|---|
| YooKassa Webhook | Принимает notification | HTTPS endpoint, секретный путь, корректный 200 ответ |
| Normalize payment | Проверяет event/status/paid | payment.succeeded, paid=true, metadata |
| Check idempotency | Ищет payment_id в журнале | Postgres unique key или другой durable storage |
| Verify payment status | Сверяет статус с API ЮKassa при споре | Basic Auth/OAuth, timeout, 401/429 |
| Update CRM deal | Меняет статус сделки и добавляет комментарий | Сумма, валюта, order_id/deal_id |
| Respond 200 | Подтверждает получение | Без stack trace и персональных данных |
Контракт входных данных payment.succeeded ¶
Ключевая практика — передавать order_id или deal_id в metadata при создании платежа. Тогда webhook можно связать с CRM без парсинга описания “Заказ №...”.
{
"type": "notification",
"event": "payment.succeeded",
"object": {
"id": "2f3c6a99-000f-5000-9000-1d2a3b4c5d6e",
"status": "succeeded",
"paid": true,
"amount": { "value": "12900.00", "currency": "RUB" },
"income_amount": { "value": "12460.00", "currency": "RUB" },
"description": "Заказ №10492",
"metadata": {
"order_id": "10492",
"deal_id": "crm-5581",
"customer_phone": "+7 916 123-45-67"
},
"created_at": "2026-05-30T10:00:00.000Z",
"test": false
}
}
Не используйте только сумму и телефон для поиска сделки: это легко ломается при частичных оплатах, доплатах и одинаковых заказах.
Проверка платежа и idempotency key (Code Node) ¶
Code Node ниже отсекает неуспешные события, проверяет metadata и формирует стабильный ключ для журнала идемпотентности.
const event = $json.event;
const payment = $json.object ?? {};
if (event !== 'payment.succeeded' || payment.status !== 'succeeded' || payment.paid !== true) {
return [{ json: { action: 'ignore', reason: 'not_successful_payment', event, status: payment.status } }];
}
const orderId = String(payment.metadata?.order_id ?? '').trim();
const dealId = String(payment.metadata?.deal_id ?? '').trim();
if (!orderId && !dealId) throw new Error('No order_id or deal_id in YooKassa metadata');
const amount = payment.amount?.value;
const currency = payment.amount?.currency ?? 'RUB';
const paymentId = payment.id;
const idempotencyKey = `yookassa:${paymentId}:succeeded`;
return [{
json: {
action: 'mark_paid',
idempotency_key: idempotencyKey,
payment_id: paymentId,
order_id: orderId,
deal_id: dealId,
amount,
currency,
crm_comment: `Оплата ЮKassa ${amount} ${currency}, payment_id=${paymentId}`,
paid_at: payment.created_at,
test_payment: payment.test === true
}
}];
Дальше этот ключ должен попасть в таблицу с уникальным индексом. Если запись уже существует, workflow не должен повторно менять CRM и отправлять уведомления.
Готовый workflow JSON: скачать и импортировать ¶
Полный workflow JSON находится в архиве сайта. Перед запуском замените Postgres credential, CRM endpoint и способ проверки платежа под вашу модель авторизации ЮKassa.
{
"name": "Nodbot - YooKassa payment to CRM with idempotency",
"nodes": [
{ "name": "YooKassa Webhook", "type": "n8n-nodes-base.webhook", "purpose": "Принять notification от ЮKassa" },
{ "name": "Normalize payment", "type": "n8n-nodes-base.code", "purpose": "Проверить event/status/paid и собрать idempotency key" },
{ "name": "Check idempotency", "type": "n8n-nodes-base.postgres", "purpose": "Не обработать payment_id дважды" },
{ "name": "Verify payment status", "type": "n8n-nodes-base.httpRequest", "purpose": "При необходимости сверить статус с API ЮKassa" },
{ "name": "Update CRM deal", "type": "n8n-nodes-base.httpRequest", "purpose": "Поставить статус Оплачено и добавить комментарий" },
{ "name": "Respond 200", "type": "n8n-nodes-base.respondToWebhook", "purpose": "Подтвердить получение уведомления" }
],
"connections": "Webhook → Normalize → Idempotency → Verify → CRM update → Respond 200"
}
Пошаговая настройка webhook ЮKassa, n8n и CRM ¶
- В ЮKassa укажите HTTPS URL webhook n8n для события
payment.succeeded. - При создании платежа сохраняйте
order_idилиdeal_idвmetadata. - В n8n импортируйте workflow JSON и настройте Postgres/CRM credentials.
- Создайте таблицу идемпотентности с уникальным ключом
idempotency_key. - Настройте обновление CRM: статус “Оплачено”, сумма, payment_id, комментарий и чек/receipt link.
- Отправьте один payload дважды и убедитесь, что второе событие не меняет сделку повторно.
Тесты перед production и проверка API ЮKassa ¶
Проверяйте четыре типа сценариев: успешный платеж, повтор successful webhook, отменённый платеж и webhook без metadata. Для каждого случая должно быть понятно, обновляет ли workflow CRM или безопасно игнорирует событие.
curl -X POST "https://YOUR-N8N-DOMAIN/webhook/yookassa-payment-to-crm" \
-H "Content-Type: application/json" \
--data @yookassa-payment-to-crm-payload.json
- Повторный
payment_idдолжен получить статус duplicate/skipped. payment.canceledне должен переводить сделку в “Оплачено”.- Webhook без
deal_id/order_idдолжен создать alert, а не искать сделку по сумме. - Ошибка CRM после записи idempotency должна иметь понятный retry/runbook.
Production-риски платежных интеграций ¶
- Нет durable idempotency. Memory cache не спасёт после рестарта n8n.
- CRM обновляется до проверки статуса. Так можно закрыть заказ по неподтверждённому событию.
- Повтор webhook вызывает повторную отгрузку. Бизнес-действие должно быть отделено от технического ответа.
- Секреты ЮKassa в execution data. Не логируйте auth headers и полный ответ API.
- Не учитываются возвраты. Для refund нужен отдельный сценарий, а не удаление payment record.
Полезные ссылки и смежные workflow ¶
Официальные документы: входящие уведомления ЮKassa и справочник API ЮKassa. Внутри Nodbot смотрите Webhook idempotency to Postgres, Retry и DLQ для HTTP Request, Postgres node и HTTP Request node.
Критерии готовности ¶
- Каждый
payment_idобрабатывается один раз. - CRM обновляется только для
payment.succeededиpaid=true. order_id/deal_idприходит из metadata, а не угадывается по сумме.- Есть журнал платежей, retry-правило и alert на ошибки CRM/API.
- Повторный webhook не отправляет второе письмо, чек или доступ.
Нужно подключить оплату без финансовых дублей?
Nodbot настроит ЮKassa → n8n → CRM с idempotency, журналом платежей, проверкой статусов и безопасным обновлением сделок.