Диагностика Ю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 таблиц и отправку сообщения менеджеру. Надёжный вариант — разделить процесс на две части:
- Webhook принимает уведомление, проверяет минимум полей и возвращает
200. - Отдельная ветка или дочерний 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 и времени получения.
Контрольный тест после исправления ¶
После правки выполните один сценарий полностью:
- Создайте тестовый платёж.
- Проверьте, что в payload есть
metadata.order_id. - Получите webhook в n8n.
- Верните
200быстро. - Убедитесь, что заказ обновился один раз.
- Повторно отправьте тот же payload вручную и проверьте, что дубль не создан.
- Сохраните 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-алерт.