Перейти к содержанию

Диагностика ЮKassa в n8n: платежи, чеки и повторы

Обновлено: 2026-05-29

Открыть мой план

Короткий ответ

Если webhook ЮKassa не обновляет заказ в n8n, сначала проверьте не CRM и не бизнес-логику, а базовый контракт доставки: публичный HTTPS URL, метод POST, production URL активного workflow, быстрый ответ HTTP 200 и сохранение payment_id или event в журнале. ЮKassa считает любой ответ кроме 200 невалидным и может повторять доставку, поэтому workflow должен сначала принять уведомление, записать факт события, а уже потом выполнять тяжёлую обработку. Для защиты от дублей используйте идемпотентность на своей стороне: храните object.id, тип события и итоговый статус заказа.

Карта симптомов

Симптом Вероятная причина Проверка в n8n
Уведомление не приходит неверный URL, workflow выключен, не тот порт/HTTPS curl, execution list, логи reverse proxy
ЮKassa шлёт событие повторно n8n не вернул 200 достаточно быстро response mode и время выполнения workflow
Заказ не обновился не связали payment_id с order_id/deal ID metadata и таблица соответствий
Появились дубли оплат повторное событие обработано как новое идемпотентность по object.id
Статус “не тот” смешаны события оплаты, отмены и возврата отдельная таблица переходов статусов
Тест работает, боевой магазин нет разные URL/магазины/ключи/события настройки магазина и production URL

Шаг 1. Проверьте URL для уведомлений

Для ЮKassa endpoint должен быть публичным и доступным снаружи. В n8n используйте production URL Webhook node, а не test URL из редактора. Test URL подходит для ручной отладки, но не должен попадать в личный кабинет ЮKassa или в API-настройку webhook.

Минимальный тест endpoint:

curl -i -X POST 'https://n8n.example.ru/webhook/yookassa-payment' \
  -H 'content-type: application/json' \
  -d '{"event":"payment.succeeded","object":{"id":"pay_debug_001","status":"succeeded"}}'

Если curl получает 404, значит проблема ещё до ЮKassa: выключен workflow, изменился path, reverse proxy не пропускает /webhook/ или n8n генерирует неправильный публичный URL. Если curl получает 2xx, переходите к проверке формата payload и события.

Шаг 2. Отвечайте ЮKassa быстро

Webhook для платежей не должен ждать CRM, рассылку, генерацию PDF, запись в 5 таблиц и отправку сообщения менеджеру. Надёжный вариант — разделить процесс на две части:

  1. Webhook принимает уведомление, проверяет минимум полей и возвращает 200.
  2. Отдельная ветка или дочерний workflow обновляет CRM, таблицу, Telegram и склад.

В n8n это можно сделать через Respond to Webhook сразу после базовой проверки, а тяжёлые действия вынести дальше. Так ЮKassa видит успешный приём, а вы не создаёте повторные доставки из-за долгого workflow.

Шаг 3. Не обновляйте заказ только по слову succeeded

Поле event удобно для маршрутизации, но для финансового решения лучше хранить весь объект платежа и сверять актуальный статус. Минимальный набор для журнала:

{
  "event": "payment.succeeded",
  "payment_id": "={{ $json.object.id }}",
  "status": "={{ $json.object.status }}",
  "amount": "={{ $json.object.amount.value }}",
  "currency": "={{ $json.object.amount.currency }}",
  "order_id": "={{ $json.object.metadata.order_id }}",
  "received_at": "={{ $now }}"
}

Если metadata.order_id пустой, webhook не сможет надёжно найти заказ. В этом случае не надо создавать новую сделку “по факту оплаты”. Лучше отправить алерт и положить событие в очередь ручной проверки.

Шаг 4. Сделайте идемпотентность на стороне n8n

ЮKassa использует ключ идемпотентности для ваших исходящих POST/DELETE-запросов к API, но входящие уведомления тоже нужно обрабатывать идемпотентно на вашей стороне. Смысл простой: повторное уведомление о том же платеже не должно повторно менять заказ, создавать дубль сделки или отправлять второй чек-лист менеджеру.

Практичная схема:

Ключ Где хранить Что делать при повторе
payment_id + event Postgres/таблица логов не запускать повторную CRM-ветку
order_id + final_status CRM custom field не переводить сделку второй раз
refund_id отдельный лог возвратов не создавать второй возврат в учёте

Для небольшого проекта можно начать с Google Sheets или Airtable, но для production лучше использовать Postgres: он позволяет поставить уникальный индекс и не зависеть от скорости таблиц.

Шаг 5. Разведите статусы оплаты, отмены и возврата

Не сводите все события к одному полю “оплачено/не оплачено”. У платежей и возвратов разные бизнес-последствия. Например, payment.succeeded может переводить сделку в “Оплачено”, payment.canceled — в “Оплата отменена”, а refund.succeeded — в отдельный статус “Возврат проведён”.

Пример таблицы переходов:

Событие Действие в CRM Комментарий
payment.waiting_for_capture ожидание подтверждения не выдавать товар автоматически
payment.succeeded заказ оплачен можно запускать fulfillment
payment.canceled оплата отменена уточнить причину, не списывать склад
refund.succeeded возврат завершён уведомить бухгалтерию/менеджера
неизвестное событие не менять заказ отправить алерт и сохранить payload

Шаг 6. Проверяйте подлинность без ложной уверенности

Не стоит считать webhook подлинным только потому, что он пришёл на “секретный” URL. Минимальная защита — длинный непредсказуемый path, фильтрация по IP на уровне proxy или firewall, проверка актуального статуса объекта через API и журнал всех неизвестных payload. Если у вас несколько магазинов или OAuth-сценарий, отдельно фиксируйте, к какому магазину относится событие.

Также не логируйте секретные ключи, Authorization-заголовки и персональные данные покупателя в открытые execution logs. Для диагностики обычно хватает payment_id, event, status, amount, order_id и времени получения.

Контрольный тест после исправления

После правки выполните один сценарий полностью:

  1. Создайте тестовый платёж.
  2. Проверьте, что в payload есть metadata.order_id.
  3. Получите webhook в n8n.
  4. Верните 200 быстро.
  5. Убедитесь, что заказ обновился один раз.
  6. Повторно отправьте тот же payload вручную и проверьте, что дубль не создан.
  7. Сохраните execution ID в журнал инцидента.

FAQ

Почему ЮKassa отправляет один и тот же webhook несколько раз?
Чаще всего endpoint не вернул HTTP 200 или вернул его слишком поздно. Повторные доставки нужно считать нормальным поведением внешнего сервиса и обрабатывать идемпотентно.

Можно ли отвечать ЮKassa после обновления CRM?
Можно, но это рискованно. Если CRM отвечает долго или временно недоступна, ЮKassa может считать уведомление неуспешным. Надёжнее быстро принять событие и обновлять CRM отдельной веткой.

Что использовать как ключ идемпотентности для входящего webhook?
Для платежа обычно достаточно пары object.id + event. Для возвратов используйте идентификатор возврата и тип события.

Почему заказ не находится в CRM?
Частая причина — при создании платежа не записали order_id, deal_id или другой внешний идентификатор в metadata. Без этой связи workflow вынужден гадать по сумме, email или времени.

Нужно ли проверять IP ЮKassa?
Да, если вы принимаете платежные события на production. IP-фильтр лучше делать на reverse proxy/firewall, а в n8n оставить журнал и fallback-алерт.