Отправим в n8n настоящий текстовый файл и номер заявки одним POST-запросом. Webhook примет multipart/form-data, проверка отсеет неподходящий ввод, Extract from File прочитает текст, а клиент получит JSON. Готовую схему и файл можно скачать и повторить локально.
Результат инструкции
На выходе будет ответ с request_id: demo-001, именем note-demo.txt, размером исходного файла в байтах и его текстом. Файл не отправляется в нейросеть, клиенту или рабочую базу. Значение action: false обозначает учебный просмотр, а не согласование заявки или запуск бизнес-действия.
Работаем только с вымышленным TXT на локальном Test URL. Workflow неактивен и не предназначен для публикации без доработок. Он принимает ровно один файл с фиксированным именем, а не любые документы. PDF, картинки, OCR, антивирусная проверка и хранение вложений здесь не реализованы.
Что понадобится
- Локальный n8n и отдельный новый workflow. Проверено на n8n 2.42.4: Webhook 2.1, Code 2, Extract from File 1.1, Respond to Webhook 1.4.
- Windows PowerShell с curl.exe. В примерах n8n открыт на http://localhost:5679; если у вас другой порт, замените адрес.
- Скачиваемые JSON-схема и note-demo.txt из этой статьи. Платная подписка, API-ключи и реальные документы не нужны.
Установить n8n локально через Docker
Шаг 1. Скачайте схему и учебный файл
Скачать n8n-webhook-file-upload.json
Сохраните оба файла в отдельную папку. Если браузер просто показывает текст, сохраните его как note-demo.txt, без дополнительного расширения .txt.txt. Для точного повторения лучше использовать скачанный оригинал: в нём UTF-8 без BOM и переводы строк LF, размер 63 байта.
Заявка: demo-001
Нужен учебный расчёт.
В оригинальном файле после второй строки тоже есть перевод строки. Ручной ввод в редакторе может дать другой размер из-за CRLF или BOM. Это не обязательно ошибка: схема считает реальные байты и поддерживает эти варианты, но основной ожидаемый ответ ниже относится к скачанному файлу.
Шаг 2. Импортируйте отдельный workflow
Создайте новый workflow, используйте Import from File и выберите n8n-webhook-file-upload.json. Должны появиться пять соединённых узлов:
Upload Request → Check Upload → Read Text → Upload Preview → Send Reply
Upload Request — Webhook, Check Upload и Upload Preview — Code, Read Text — Extract from File, Send Reply — Respond to Webhook. Сохраните схему, но не публикуйте её. Не переносите узлы в действующий workflow, пока не получите учебный ответ.
Шаг 3. Настройте Webhook на multipart-запрос
| Поле Webhook | Значение | Зачем |
|---|---|---|
| HTTP Method | POST | Команда curl отправит форму |
| Path | ezgpt-file-upload | Путь учебного endpoint |
| Authentication | None, только локальный тест | Не открывать такой endpoint в сеть |
| Respond | Using Respond to Webhook node | Ответ вернёт Send Reply |
В Options оставьте настройки по умолчанию. Для этой схемы не включайте Raw Body и Binary Property и не задавайте переименование binary-поля. multipart/form-data разбирается на текстовые поля и файлы: request_id будет в $json.body, а файл из поля формы document — в binary.document.
Это не отправка всего тела запроса как одного бинарного потока. Настройки для raw binary нельзя механически переносить на multipart: иначе последующие узлы увидят другую структуру. В импортированной схеме уже выставлен нужный вариант.
Шаг 4. Проверьте файл и номер заявки
В Check Upload выберите Run Once for All Items и JavaScript. Код уже есть в JSON-схеме; при ручной сборке вставьте его целиком:
Показать код (33 строк)
const items = $input.all();
if (items.length !== 1) throw new Error('Expected one request');
const item = items[0];
const body = item.json.body;
if (!body || typeof body !== 'object' || Array.isArray(body) ||
Object.keys(body).length !== 1 ||
typeof body.request_id !== 'string' ||
!/^demo-[0-9]{3}$/.test(body.request_id)) {
throw new Error('Expected only request_id: demo-001');
}
const fields = Object.keys(item.binary ?? {});
if (fields.length !== 1 || fields[0] !== 'document') {
throw new Error('Expected exactly one file in document');
}
const file = item.binary.document;
if (file.fileName !== 'note-demo.txt' || file.mimeType !== 'text/plain') {
throw new Error('Expected note-demo.txt with text/plain');
}
const buffer = await this.helpers.getBinaryDataBuffer(0, 'document');
if (buffer.length === 0 || buffer.length > 4096) {
throw new Error('File must contain 1-4096 bytes');
}
const text = buffer.toString('utf8');
if (!Buffer.from(text, 'utf8').equals(buffer)) {
throw new Error('File must be valid UTF-8');
}
if (!text.replace(/^\uFEFF/, '').trim() || text.includes('\u0000')) {
throw new Error('Expected nonempty text without NUL');
}
return [{
json: { request_id: body.request_id, file_name: 'note-demo.txt', bytes: buffer.length },
binary: { document: file },
}];
Разрешено только поле request_id вида demo-001 и один файл в document. Проверяется фиксированное имя note-demo.txt, заявленный тип text/plain, размер от 1 до 4096 байт, корректный UTF-8, непустой текст и отсутствие NUL. Два файла в одном поле тоже не пройдут: в проверенной версии n8n создаёт document0 и document1, а схема ждёт только document.
getBinaryDataBuffer читает настоящие байты, в том числе когда n8n хранит binary на диске. Не пытайтесь читать файл через item.binary.document.data как обычную строку: там может быть служебная ссылка. После проверки код пропускает только нужные поля, без входящих headers, query и cookies.
Имя и MIME передаёт отправитель, поэтому их совпадение не доказывает безопасность файла. Проверка 4096 байт выполняется уже внутри workflow, после получения загрузки. Это не лимит входящего трафика и не защита сервера от больших запросов.
Шаг 5. Прочитайте TXT через Extract from File
Откройте Read Text. Выберите Operation: Extract From Text File, Input Binary Field: document, Destination Output Field: text. В Options выставьте File Encoding: utf8, Strip BOM: включено, Keep Source: JSON. Это соответствует operation: text в скачиваемой схеме.
Keep Source сохраняет request_id, file_name и bytes из проверки, а текст появится в отдельном поле text. Оригинальный binary далее не нужен. Если оставить только Binary вместо JSON, потеряется номер заявки; если указать data вместо document, узел не найдёт файл.
Узел читает текст, но не выполняет его и не разбирает как JSON. BOM в начале UTF-8 удаляется из результата; bytes остаётся размером исходного файла с BOM. Переводы строк LF или CRLF сохраняются.
Шаг 6. Подготовьте ограниченный JSON-ответ
В Upload Preview выберите Run Once for All Items, JavaScript и проверьте код:
const item = $input.first();
const { request_id, file_name, bytes, text } = item.json;
if (typeof text !== 'string' || !text.trim() || text.charCodeAt(0) === 0xFEFF) {
throw new Error('Check Extract from File: text field and Strip BOM');
}
if (!/^demo-[0-9]{3}$/.test(request_id ?? '') ||
file_name !== 'note-demo.txt' || !Number.isInteger(bytes)) {
throw new Error('Check Keep Source: JSON');
}
return [{ json: { status: 'preview', request_id, file_name, bytes, text, action: false } }];
Этот узел проверяет, что после извлечения сохранились метаданные и непустое поле text, затем собирает ответ только из известных полей. Не заменяйте его возвратом всего входа Webhook. Для примера возвращаем текст собственного небольшого файла; для реальных договоров и персональных данных такое отражение содержимого может быть недопустимо.
В Send Reply задайте Respond With: First Incoming Item и Response Code: 200. В Response Headers добавьте Cache-Control: no-store. Во всех рабочих узлах оставьте On Error: Stop Workflow, без Continue On Fail и без Always Output Data. Иначе ошибка проверки может попасть в ветку успешного ответа.
Шаг 7. Включите ожидание Test URL
Откройте Upload Request, выберите Test URL и нажмите Listen for test event или Execute workflow. Скопируйте фактический URL своего экземпляра. Ниже используется http://localhost:5679/webhook-test/ezgpt-file-upload.
Пока слушатель не запущен, Test URL не работает. В проверенном режиме он принимает один успешный запрос, после чего перед следующим тестом нужно снова включить ожидание. Production URL у неопубликованной схемы не зарегистрирован. Открытие ссылки в адресной строке отправляет GET, а здесь нужен POST.
Шаг 8. Отправьте файл и поле формы
Откройте папку со скачанным note-demo.txt в Проводнике и запустите в ней PowerShell. Проверьте наличие файла:
Get-Item -LiteralPath .\note-demo.txt | Select-Object Name, Length
Должны быть имя note-demo.txt и Length 63 для оригинала. Затем выполните одну строку, заменив адрес при необходимости:
curl.exe --include --max-time 15 --form "request_id=demo-001" --form "document=@note-demo.txt;type=text/plain" "http://localhost:5679/webhook-test/ezgpt-file-upload"
Символ @ заставляет curl читать содержимое файла. Без него отправится строка с именем, а не файл. Не добавляйте вручную Content-Type: multipart/form-data: curl сам создаёт границу частей boundary и правильный заголовок. В Windows используйте именно curl.exe, а не неоднозначный псевдоним curl.
Флаг --include выводит HTTP-заголовки перед JSON. Если curl пишет, что не может прочитать файл, запрос ещё не отправлен: проверьте текущую папку и расширение. Не исправляйте это подстановкой реального рабочего документа.
Шаг 9. Сверьте ответ и выполнение
Ожидайте HTTP 200, заголовок Cache-Control: no-store и следующий JSON для скачанного оригинала:
{
"status": "preview",
"request_id": "demo-001",
"file_name": "note-demo.txt",
"bytes": 63,
"text": "Заявка: demo-001\nНужен учебный расчёт.\n",
"action": false
}
request_id должен совпасть с полем формы, text — с текстом файла, bytes — с его размером. Кириллица должна читаться нормально. Экранированные Unicode-последовательности в JSON могут быть допустимым представлением текста; сломанные символы и неверный текст — нет.
| Где проверить | Ожидается | Что это доказывает |
|---|---|---|
| Upload Request | body.request_id и Binary: document | Пришёл multipart, а не строка имени |
| Check Upload | request_id, file_name, bytes | Проверки пройдены |
| Read Text | text и исходные JSON-поля | Файл прочитан, метаданные сохранены |
| Send Reply | 200 и ограниченный JSON | Клиент получил нужный результат |
Не считайте зелёный Webhook достаточным результатом: весь путь должен закончиться Send Reply. История n8n может сохранять текст, метаданные и binary входящего запроса. Даже при отсутствии записи в вашу бизнес-базу это не означает, что файл нигде не хранится. Для реальных данных нужны отдельные правила доступа и удаления executions.
Шаг 10. Проверьте отказ без файла
Снова включите ожидание Test URL и отправьте запрос только с номером заявки:
curl.exe --include --max-time 15 --form "request_id=demo-001" "http://localhost:5679/webhook-test/ezgpt-file-upload"
В проверенном n8n 2.42.4 эта учебная схема вернула HTTP 500: Check Upload остановился с Expected exactly one file in document, Send Reply не запустился. Подробность смотрите в ошибке execution, а не ожидайте её обязательно в теле ответа curl. Это преднамеренно простой сценарий остановки, не готовый обработчик клиентских ошибок с 400 или 422.
Для дополнительной проверки замените имя поля document в успешной команде на file, снова включите ожидание и повторите. Файл не должен пройти проверку. Затем верните исходную команду и убедитесь, что успешный результат повторяется.
Если результат отличается
| Симптом | Вероятная причина | Что исправить |
|---|---|---|
| 404 | Нет слушателя, неверный метод или URL | Включить Test URL и отправить POST |
| curl не читает файл | Другая папка или двойное расширение | Проверить Get-Item и путь после @ |
| 500 в Check Upload | Нет document, лишние поля или неподходящий файл | Сверить форму и ограничение 4096 байт |
| Read Text не видит binary | Указано data вместо document | Исправить Input Binary Field |
| 500 в Upload Preview | Потеряны text или метаданные | Сверить Destination и Keep Source: JSON |
Что изменить перед реальным использованием
Не публикуйте этот workflow с Authentication None. Для сетевого доступа нужны HTTPS, проверка доступа, лимиты размера на входе и частоты запросов, сроки хранения загрузок и истории. Используйте согласованный контракт полей, а не случайное имя файла клиента как путь на сервере.
Настроить Header Auth для Webhook
Если нужен ответ 400 или 422 на неподходящий файл, сначала спроектируйте отдельную ветку проверки и Respond to Webhook для отказа. В этой схеме исключение останавливает выполнение с 500. Для PDF потребуется другой обработчик; отсканированные страницы могут требовать OCR. Проверка UTF-8 не заменяет антивирус, проверку формата и ручное согласование дальнейших действий.
Обратная задача: вернуть файл из Webhook
Что проверено редакцией
17 октября 2026 года публичная JSON-схема проверена реальными multipart-запросами в отдельном локальном Docker-экземпляре n8n 2.42.4 с binary на диске. Все 26 проверок прошли. Точный ответ 200 получен для исходного UTF-8 TXT, CRLF, UTF-8 BOM и многоязычного текста; команда curl.exe проверена отдельно. Подтверждены отказы 500 без файла, с другим полем, двумя файлами, неверным именем или MIME, отсутствующим или неверным request_id, лишним полем, пустым или пробельным файлом, размером 4097 байт, неверным UTF-8, NUL, JSON вместо multipart и тремя ошибками настройки Extract from File. При отказах Send Reply не запускался. Проверены 404 без слушателя, при GET вместо POST, повторе после успешного теста и у неактивного Production URL. Настройки сверены с официальной документацией и кодом Webhook и Extract from File из этой версии. Контейнер был привязан только к loopback и остановлен; существующие workflows не менялись. Клики редактора, загрузка через браузерную форму, n8n Cloud, production, reverse proxy, OCR и проверка файлов на вредоносность не тестировались. Обложка — редакционная AI-иллюстрация, не снимок интерфейса.
Частые вопросы
Почему имени файла в JSON недостаточно?
Строка note-demo.txt не содержит байты файла. Для этой схемы нужен multipart-запрос с файловым полем document; в curl файл передаётся через @.
Нужно включить Binary Property или Raw Body?
Нет, не для описанной multipart-схемы. Оставьте Options Webhook по умолчанию: файл формы document попадёт в binary.document. Raw binary — другой способ передачи.
Можно отправить PDF или картинку?
Нет. Учебный пример принимает только note-demo.txt с текстом UTF-8. Для PDF, изображений и OCR нужны другие операции и проверки.
Почему размер отличается от 63 байт?
63 байта относится к скачанному оригиналу. CRLF вместо LF даст 65, UTF-8 BOM с исходными LF — 66. Схема считает исходные байты, а BOM убирает только из извлечённого текста.
Можно сразу открыть endpoint для клиентов?
Нет. Нужны авторизация, HTTPS, входные лимиты, управление хранением и отдельные ответы на ошибки. None и HTTP здесь используются только для собственного локального теста.