Workflow подготовил черновик, но продолжать без ответа человека нельзя. Узел Wait позволяет остановить выполнение и возобновить его отдельным HTTP-запросом. Соберём локальный пример: черновик → пауза → approved или rejected. Проверим неверный ответ, истечение срока и перезапуск n8n.
Результат инструкции
В схеме ezGPT - Wait for Review будет четыре узла. До ответа выполнение имеет статус Waiting; после ответа тот же execution получает итог approved, rejected, invalid_request или expired. В выходных данных останется исходный черновик, а action_performed всегда будет false: ничего не публикуем, не отправляем и не оплачиваем.
Это учебная проверка паузы и решения, не готовая система полномочий. Владение адресом продолжения не доказывает, кто ответил. В рабочем процессе нужны авторизация, проверка роли согласующего и защита последующих действий от дублей. AI-модель для этой сборки не требуется.
Что понадобится
- Локальный n8n с сохраняемыми данными. Пример проверен на версии 2.42.4 и Windows с PowerShell.
- Права создавать и вручную запускать свои workflows. Используйте отдельную учебную схему.
- Редактор по адресу localhost:5679 для нашей Docker-установки. Другой порт допустим, но адрес продолжения должен вести на ваш доступный экземпляр.
- Вымышленный черновик; реальные данные клиентов и ключи не нужны.
Установка n8n через Docker с сохраняемым томом
Шаг 1. Проверьте адрес для продолжения
В Docker-установке по нашей инструкции откройте compose.yml в папке $HOME/n8n-local. В существующую секцию environment сервиса n8n добавьте одну строку N8N_WEBHOOK_URL, не заменяя остальные настройки. Для другого локального порта подставьте свой порт.
N8N_WEBHOOK_URL: "http://localhost:5679/"
Примените конфигурацию. Это нужно, чтобы n8n генерировал адрес продолжения с портом компьютера, а не недоступным вам внутренним портом контейнера.
Set-Location "$HOME/n8n-local"
docker compose up -d n8n
docker compose ps
Дождитесь доступного редактора. Не меняйте привязку 127.0.0.1 на 0.0.0.0 и не удаляйте том. На установленном сервере или Cloud этот шаг не выполняйте: адрес и доступ настраивает администратор, а вариант без аутентификации ниже предназначен только для локального обучения.
Шаг 2. Импортируйте учебный workflow
Создайте новый workflow и выберите Import from File в меню редактора. Загрузите JSON; если браузер показывает его как текст, сначала сохраните файл через «Сохранить как». Название схемы — ezGPT - Wait for Review.
Manual Trigger → Prepare Request → Wait for Review → Read Decision
На полотне должны быть четыре узла. Prepare Request и Read Decision — Code, Wait for Review — Wait. При ручной сборке добавьте их в таком же порядке и сохраните точные имена: код после паузы обращается к Prepare Request.
Шаг 3. Подготовьте черновик и срок ответа
Откройте Prepare Request. Language: JavaScript, Mode: Run Once for All Items. В импортированном workflow уже есть код ниже. Он создаёт одну учебную заявку с уникальным для этого выполнения request_id.
return [{
json: {
request_id: 'R-' + $execution.id,
draft: 'Учебный черновик: встреча команды в пятницу.',
status: 'awaiting_review',
expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
resume_url: $execution.resumeUrl,
},
pairedItem: { item: 0 },
}];
request_id строится из execution ID. expires_at задаёт срок ответа — десять минут от подготовки заявки, в UTC. resume_url берётся из $execution.resumeUrl; его генерирует n8n для конкретного выполнения. Вручную придумывать адрес или подставлять ID другого execution нельзя.
В примере только одна заявка на выполнение. Не меняйте его на массив заказов без отдельной схемы обработки: код ниже использует первый item и не является пакетным согласованием.
Шаг 4. Настройте Wait на POST
В Wait for Review выберите Resume: On Webhook Call, Authentication: None, HTTP Method: POST, Respond: Immediately, Response Code: 200. В Options оставьте Webhook Suffix пустым. Никакой обычный Webhook перед Wait не нужен.
Включите Limit Wait Time. Limit Type: After Time Interval, Amount: 10, Unit: Minutes. После достижения лимита n8n продолжит цепочку даже без HTTP-ответа; поэтому узел после Wait должен отличать отсутствие ответа от одобрения.
None допустимо здесь только потому, что локальный экземпляр доступен этому компьютеру и схема ничего не отправляет. Полный resume_url содержит служебную подпись в проверенной версии; храните его как секрет. Подпись адреса не проверяет личность согласующего. Для внешнего доступа сначала нужен безопасный канал и отдельная аутентификация.
Шаг 5. Проверьте решение после паузы
В Read Decision выберите JavaScript и Run Once for All Items. Вставьте код ниже или сверяйте его с импортированным. Он берёт исходный черновик из Prepare Request, а решение — из JSON-тела запроса, полученного Wait.
const request = $('Prepare Request').first().json;
const incoming = $input.first().json;
const isCallback = Object.prototype.hasOwnProperty.call(incoming, 'headers');
const body = incoming.body;
let status = 'expired';
if (isCallback && Date.now() < Date.parse(request.expires_at)) {
status = 'invalid_request';
if (body && typeof body === 'object' && !Array.isArray(body)
&& body.request_id === request.request_id) {
if (body.decision === 'approved') status = 'approved';
if (body.decision === 'rejected') status = 'rejected';
}
}
return [{
json: {
request_id: request.request_id,
draft: request.draft,
status,
action_performed: false,
},
pairedItem: { item: 0 },
}];
approved принимается только при совпавшем request_id и неистёкшем expires_at. rejected — явный отказ с таким же ID. Неверное тело или значение решения даёт invalid_request. Если Wait продолжился по таймеру, а не по запросу, получится expired. Проверка времени не разрешит одобрение после срока, даже если таймер ещё не возобновил цепочку.
В этом локальном примере наличие headers в выходе Wait отличает callback от автоматического продолжения. Это правило относится к проверенному workflow, а не к любым входным данным. Нельзя подставлять пользовательский JSON вместо выхода Wait и считать такую подстановку настоящим событием.
Шаг 6. Запустите всю цепочку до Waiting
Сохраните workflow. Запустите целиком с Manual Trigger, не отдельным Execute step у Wait. Публиковать эту ручную учебную схему не нужно: в проверке она оставалась неопубликованной. Это отличается от Error Trigger, которому нужен автоматический исходный запуск.
Откройте текущее выполнение в Executions. Ожидается Waiting на Wait for Review, а Read Decision ещё не выполнен. Запишите execution ID. Если сразу видите Success, проверьте режим Wait, его включённость и лимит: запуск только одной части цепочки не повторяет инструкцию.
Когда нужен отдельный Error Trigger
Шаг 7. Возьмите адрес и ID из этого выполнения
В ожидающем execution откройте OUTPUT Prepare Request. Скопируйте целиком resume_url и отдельно request_id. Статус заявки должен быть awaiting_review, а expires_at — примерно через десять минут после подготовки. Адрес должен вести на localhost:5679 для нашей установки.
Не используйте ссылку из предыдущего теста. Для каждого нового выполнения n8n создаёт свой адрес. Копируйте всю строку, включая ?signature= и всё после него, не редактируя значение подписи. Не открывайте ссылку как обычную страницу: браузер отправит GET, а наш Wait ожидает POST.
Если адрес содержит неправильный порт, исправьте N8N_WEBHOOK_URL, примените конфигурацию и создайте новое выполнение. Не подставляйте угаданный URL и не переносите адрес на сторонний сайт.
Шаг 8. Отправьте одобрение
В PowerShell замените две строки-заглушки значениями из шага 7. Введите адрес внутри одинарных кавычек, сохранив подпись. ConvertTo-Json сформирует корректное тело запроса. Отправьте его до expires_at.
$resumeUrl = 'PASTE_FULL_RESUME_URL'
$requestId = 'PASTE_REQUEST_ID'
$body = @{
request_id = $requestId
decision = 'approved'
} | ConvertTo-Json -Compress
Invoke-RestMethod -Method Post -Uri $resumeUrl `
-ContentType 'application/json' -Body $body
Ответ 200 говорит о принятом запросе, а не о выполненном бизнес-действии. Откройте тот же execution ID и дождитесь Success. В OUTPUT Read Decision должны быть исходные request_id и draft, status: approved, action_performed: false. Новый execution для продолжения не создаётся.
Если ответ пришёл после срока, ожидайте expired, а не approved. Success — статус успешного выполнения кода n8n; он не равен одобрению. Решение хранится отдельно в поле status.
Шаг 9. Проверьте использованный адрес
Повторите команду из шага 8 с тем же resume_url после завершения execution. В проверенном n8n 2.42.4 сервер вернул 409: выполнение уже закончено. PowerShell покажет HTTP-ошибку; для этого теста это ожидаемо.
Проверьте Read Decision: результат approved не должен измениться, второе решение не появляется. Этот последовательный тест не доказывает защиту любых внешних действий от конкурентных запросов. Перед рабочей отправкой сообщения или платежом всё равно нужна идемпотентность.
Шаг 10. Проверьте отказ в новом выполнении
Снова запустите весь workflow с Manual Trigger. Дождитесь Waiting, скопируйте новый resume_url и request_id в PowerShell. Убедитесь, что execution ID отличается от первого. Не оставляйте в переменных прежнюю ссылку.
В команде шага 8 замените decision на rejected и отправьте POST. Тот же новый execution должен завершиться Success с status: rejected и action_performed: false. Черновик не меняется: код сохраняет подготовленный текст, а не заменяет его данными callback.
Шаг 11. Проверьте неверное решение и ID
Для каждого теста создавайте отдельное новое выполнение. В первом передайте decision: approved, но request_id: R-wrong. Во втором оставьте правильный ID, но отправьте decision: approve — без окончания d. В обоих случаях результат должен быть invalid_request, а action_performed — false.
Также можно исключить decision из JSON: это не согласие по умолчанию. Неверный запрос с валидным адресом уже продолжает Wait и завершает нашу схему. Нельзя затем «исправить» решение повторным POST к той же ссылке: для новой попытки создайте новое выполнение. В рабочей версии проверку входа можно вынести перед механизмом продолжения.
Неправильный метод и неправильная подпись ведут себя иначе. GET по валидной ссылке в тесте вернул 404 и оставил Waiting; POST без signature вернул 401 и тоже не продолжил выполнение. После этих двух ошибок валидный POST ещё сработал. Не удаляйте подпись из рабочего запроса ради удобства.
Шаг 12. Проверьте истечение срока
В учебном workflow временно поменяйте в Prepare Request выражение Date.now() + 10 * 60 * 1000 на Date.now() + 30 * 1000. В Wait for Review поставьте Amount: 30, Unit: Seconds. Сохраните и начните новое выполнение; старый запуск не является тестом новых настроек.
Не отправляйте POST. Дождитесь Success и посмотрите Read Decision: status expired, action_performed false. Возобновление может произойти позже указанного времени из-за работы планировщика и нагрузки; не требуйте точного перехода в тридцатую секунду. В нашей проверке срок автоматического ожидания был сокращён до трёх секунд, а тест ждал фактического завершения.
После завершения отправьте approved по этой ссылке: проверенная версия вернула 409, а expired не изменился. Если запрос пришёл после expires_at, но выполнение ещё Waiting, код тоже вернёт expired после продолжения. Тайм-аут не считается согласованием.
Верните обе настройки к десяти минутам: исходное выражение в Prepare Request и Amount: 10, Unit: Minutes в Wait. Сохраните схему.
Шаг 13. Перезапустите n8n во время паузы
Создайте новое десятиминутное выполнение и дождитесь Waiting. Запишите его execution ID, request_id и полный resume_url. Только для локальной Docker-установки из нашей инструкции выполните:
Set-Location "$HOME/n8n-local"
docker compose restart n8n
docker compose ps
Дождитесь редактора и откройте прежний execution. Он должен оставаться Waiting, пока срок ещё не истёк. Не нажимайте Manual Trigger повторно. Отправьте approved с сохранёнными request_id и адресом: должен завершиться тот же execution с approved.
Такой тест прошёл с сохраняемым хранилищем нашего экземпляра. Он не доказывает восстановление после потери базы. Не удаляйте volume и не используйте down -v. На Cloud не выполняйте локальные команды; возможность перезапуска согласуйте отдельно.
Шаг 14. Сверьте результаты и завершите тест
Проверьте финальный OUTPUT по таблице. Во всех четырёх случаях action_performed должен быть false. Не считайте зелёный Success разрешением выполнить действие — смотрите именно status.
| Запрос | Итог status | Действие |
|---|---|---|
| Верный ID, approved, срок не истёк | approved | Ничего не отправлено |
| Верный ID, rejected, срок не истёк | rejected | Ничего не отправлено |
| Неверный ID или решение | invalid_request | Ничего не отправлено |
| Нет ответа или срок истёк | expired | Ничего не отправлено |
Проверьте Executions своей учебной схемы. Завершите оставшиеся Waiting валидным rejected до срока или дождитесь expired. Не удаляйте историю ради красивого результата. Workflow остаётся ручным и неопубликованным, API-ключей и новых внешних интеграций в нём нет.
Если результат отличается
| Симптом | Вероятная причина | Что проверить |
|---|---|---|
| Connection refused | Неверный адрес или порт | N8N_WEBHOOK_URL и доступность n8n |
| GET вернул 404 | Wait ждёт POST | Метод в PowerShell и Wait |
| POST вернул 401 | Служебная подпись отсутствует | Полный resume_url без изменений |
| POST вернул 409 | Execution уже завершён | Новый запуск и новая ссылка |
| Success, но invalid_request | Неверный ID или decision | request_id, approved/rejected, JSON |
| Нет автоматического продолжения | Лимит выключен или задержка | Limit Wait Time и состояние сервиса |
Что не стоит автоматизировать сразу
Не подключайте к approved оплату, удаление, подписание или публикацию. Это учебная переменная, а не доказательство полномочий. Authentication None не подходит для открытого доступа: настройте аутентификацию, проверяйте роль отвечающего, срок и привязку решения к конкретной версии черновика.
resume_url с подписью виден в OUTPUT и может сохраняться в истории Executions. Не публикуйте его в статье, общем чате или открытых логах. Итоговый Read Decision не возвращает ссылку, но это не очищает прежние данные выполнения. Согласуйте доступ и сроки хранения истории.
Для пачки заявок, нескольких согласующих, повторного исправления ответа и борьбы с дублями понадобится отдельная модель состояния. Здесь один execution, одна заявка и один Wait. Две одновременные доставки и многопользовательское согласование не тестировались.
Как сохранять результаты отдельно в Data Tables
Что проверено редакцией
13 октября 2026 года схема проверена на n8n 2.42.4 в отдельном локальном экземпляре, доступном только на 127.0.0.1. Тринадцать тестов: ожидание и approved в том же execution; повторный вызов завершённого адреса; rejected; отсутствие decision; неверный ID; неизвестное решение; текст вместо JSON; GET вместо POST с последующим валидным POST; отсутствие signature с последующим валидным POST; автоматический тайм-аут; поздний callback после завершения; callback при уже истёкшем expires_at, пока Wait ещё ждёт; перезапуск во время ожидания и продолжение с прежним ID. Во всех итогах action_performed false, черновик взят из Prepare Request. GET вернул 404, отсутствующая подпись — 401, повтор и поздний callback после завершения — 409 в проверенной версии. Ручные запуски выполнялись штатным API n8n, workflow оставался неопубликованным. Первая проба уточнила код ответа 409, следующая — 401; короткий тест тайм-аута потребовал ожидания фактической работы планировщика, а не ровно трёх секунд. Финальный набор тестов прошёл. Cloud, кликовые действия редактора n8n, внешняя аутентификация, конкурентные запросы, потеря базы и реальные публикации не тестировались. Рабочие workflows не менялись, выделенные контейнеры остановлены. Обложка — редакционная AI-иллюстрация.
Частые вопросы
Нужно ли публиковать workflow для этого теста?
Нет. Проверенный пример запускается с Manual Trigger и продолжает тот же execution через Wait. Обычный production Webhook или Error Trigger имеют свои требования; не переносите этот вывод на другие схемы.
Чем resume_url отличается от обычного Webhook URL?
Он относится к конкретному ожидающему выполнению. Для нового execution нужен новый адрес. Копируйте его целиком из Prepare Request, включая подпись, и не используйте адрес завершённого запуска.
Вызов ссылки означает одобрение?
Нет. Он продолжает Wait. Одобрение появляется только после проверки request_id, decision и expires_at в Read Decision. Этот пример не проверяет личность и права человека.
Что будет без ответа?
При включённом лимите Wait продолжит цепочку по времени, а наш код вернёт expired. Фактическое возобновление может задержаться. Тайм-аут не должен разрешать публикацию или другое действие.
Можно ли исправить invalid_request новым POST?
Не в этом примере: первый запрос с валидным адресом уже завершает ожидание. После invalid_request начните новое выполнение с новой ссылкой. Для исправлений без закрытия ожидания нужна отдельная проверка входа и модель состояний.