Привет. Я в своей мастерской такие штуки люблю за то, что они быстро отбивают время: человек заполнил форму, заявка улетела в n8n, дубль отсеялся, а мне или менеджеру сразу прилетел сигнал в Telegram. Всё, цепочка живая, ничего не потерялось, и не надо потом ковырять почту в поисках “а кто там писал утром”.
Если нужен короткий ответ по запросу “форма заявки n8n”, то мой рабочий вариант такой: кастомная HTML-форма на странице отправляет POST в Webhook, дальше я нормализую телефон и email, собираю ключ для дедупликации, режу повторы через Remove Duplicates и только новые заявки отправляю в Telegram. Ни тяжёлой CRM на старте, ни лишних узлов тут не требуется.
Ниже покажу саму схему, кусок HTML, логику защиты от дублей запросов, шаблон уведомления и список мест, где народ чаще всего спотыкается: тестовый URL, CORS, повторный клик по кнопке, кривой chat_id и слишком длинный ответ вебхука.
Содержание
- Что именно собираем
- Как устроен workflow по узлам
- Кастомный HTML формы
- Как режу дубли заявок
- Что улетает в Telegram
- Где схема обычно ломается
- Чек-лист перед публикацией

Что именно собираем
Смысл схемы простой: на сайте стоит кастомная HTML-форма для n8n, человек оставляет имя, телефон, услугу и комментарий, а n8n принимает это через webhook для формы, проверяет, не прилетала ли уже такая заявка, и шлёт уведомление в Telegram. Это удобно для лендинга, страницы услуг, мастерской, небольшого сервиса и вообще любой истории, где хочется быстро ловить лиды и не утонуть в ручной рутине.
Я обычно собираю это в таком составе:
- Страница с кастомной HTML-формой.
- Webhook в n8n c методом POST.
- Set или Code для нормализации данных.
- Remove Duplicates для защиты от повторной отправки.
- IF или Switch для ветки “новая заявка / дубль”.
- Telegram node для уведомления.
- Respond to Webhook для аккуратного ответа форме.
Почему я беру именно Webhook, а не только Form Trigger? Потому что у встроенной формы n8n есть кастомный HTML, но он чистится и режет часть вольностей. Если надо вставить свой CSS, JavaScript и полностью рулить поведением кнопки отправки, мне проще поднять обычную HTML-страницу и стучаться в webhook. А вот если хочешь обойтись внутренними формами, помни: у них тоже можно вставлять Custom HTML-блоки и красиво оформить финальную страницу после отправки.
Кстати, по соседним темам у тебя на сайте уже есть хорошие материалы. Если нужно глубже понять подключение вебхука и защиту от дублей запросов, там как раз лежит полезная база. А для тех, кто ещё не подружил n8n с ботом, пригодится техническая инструкция по Telegram Bot API.
Как устроен workflow по узлам
Вот мой базовый расклад, который я ставлю, когда нужен готовый workflow n8n для заявок и не хочется городить комбайн на пустом месте.
| 1. Webhook | Принимает POST от формы. | Path: lead-form, Method: POST, Response mode: Using Respond to Webhook node. |
| 2. Code | Чистит телефон, email, имя, услугу и собирает lead_key. | Лучше сразу привести всё к одному виду. |
| 3. Remove Duplicates | Отсекает повторные заявки. | Operation: Remove Items Processed in Previous Executions, Keep Items Where: Value Is New. |
| 4. IF | Разводит новую заявку и дубль по разным веткам. | Проверяю флаг, прошло ли что-то дальше после дедупликации. |
| 5. Telegram | Шлёт уведомление менеджеру или в рабочий чат. | Текст собираю из нормализованных полей. |
| 6. Respond to Webhook | Возвращает форме понятный ответ. | JSON или HTML со статусом “ok”. |
Я люблю держать ответ вебхука коротким и быстрым. Форма получила подтверждение, пользователь увидел “заявка принята”, а всё тяжёлое уже происходит после. Если дальше ты захочешь дописать сохранение в таблицу, Notion или автоматическую генерацию коммерческого предложения, это легко наращивается. В тему сюда отлично ложится материал про автоформирование КП после заполнения формы.
Отдельный момент: если тестируешь, не забудь, что у webhook есть test URL и production URL. На тестовом всё выглядит бодро, а потом человек с сайта жмёт кнопку, и тишина, потому что workflow не активирован или в форме остался тестовый адрес. Старый, но вечный косяк.
Кастомный HTML формы
Теперь к мясу. Ниже минимальный пример, который я бы воткнул на страницу услуг. Он не претендует на премию за дизайн, зато это честная кастомная HTML-форма для n8n, которую можно быстро оживить и уже потом докрутить стили под свой сайт.
<form id="leadForm">
<input type="text" name="name" placeholder="Ваше имя" required>
<input type="tel" name="phone" placeholder="Телефон" required>
<input type="text" name="service" placeholder="Что нужно сделать" required>
<textarea name="comment" placeholder="Коротко опишите задачу"></textarea>
<button type="submit" id="leadBtn">Отправить заявку</button>
<p id="leadStatus"></p>
</form>
<script>
const form = document.getElementById('leadForm');
const btn = document.getElementById('leadBtn');
const status = document.getElementById('leadStatus');
form.addEventListener('submit', async (e) => {
e.preventDefault();
btn.disabled = true;
status.textContent = 'Отправляю...';
const fd = new FormData(form);
const payload = {
name: fd.get('name')?.trim() || '',
phone: fd.get('phone')?.trim() || '',
service: fd.get('service')?.trim() || '',
comment: fd.get('comment')?.trim() || '',
source: 'site-form',
page: window.location.pathname
};
try {
const res = await fetch('/webhook/lead-form', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
});
const data = await res.json();
status.textContent = data.message || 'Готово, заявка ушла.';
form.reset();
} catch (err) {
status.textContent = 'Не получилось отправить, попробуйте ещё раз.';
btn.disabled = false;
}
});
</script>
Что я бы докрутил сразу, даже если спешу. Первое — блокировка кнопки после клика, чтобы человек не устроил три отправки подряд. Второе — поле source, чтобы потом было видно, откуда пришёл лид: сайт, квиз, отдельный лендинг или форма после рекламы. Третье — поле page, потому что потом приятно понимать, какая именно страница генерит заявки.
Если HTML-страница лежит не на том же домене, где крутится n8n, в webhook надо настроить Allowed Origins. Иначе браузер сделает кислую мину, а ты увидишь классическую историю: “вроде форма отправляется, а на деле ничего не уходит”.
Как режу дубли заявок
Вот тут и начинается взрослая жизнь. Повторы летят по разным причинам: человек дважды нажал кнопку, браузер повторил запрос, внешний слой переслал POST ещё раз, или ты сам случайно дёрнул форму при тесте. Поэтому дедупликация заявок по телефону или email — не прихоть, а санитарный минимум.
Я делаю так: в Code node собираю чистые поля и вычисляю один понятный ключ. Обычно это нормализованный телефон. Если телефона нет, беру email. Если нет и его, собираю запасной ключ из имени и услуги. Пример логики такой:
const digits = ($json.phone || '').replace(/\D/g, '');
const phone_normalized = digits ? digits : '';
const email_normalized = ($json.email || '').trim().toLowerCase();
const service_normalized = ($json.service || '').trim().toLowerCase();
const name_normalized = ($json.name || '').trim().toLowerCase();
const lead_key =
phone_normalized ||
email_normalized ||
`${name_normalized}|${service_normalized}`;
return [{
json: {
...$json,
phone_normalized,
email_normalized,
service_normalized,
name_normalized,
lead_key
}
}];
Дальше в Remove Duplicates ставлю операцию Remove Items Processed in Previous Executions, а в Value to Dedupe On — lead_key. На практике это даёт очень бодрую защиту от дублей запросов. Если заявка уже была, новый прогон просто не пойдёт дальше по ветке оповещения.
Тут есть важная развилка. Иногда люди пытаются хранить историю дублей в static data через код. Я так делаю редко и только там, где поток маленький. Для активной формы заявок мне спокойнее использовать штатный Remove Duplicates, потому что static data в документации помечен как экспериментальный механизм, да и на частых вызовах это не лучший фундамент.
Если хочешь посмотреть близкую по смыслу реализацию, у тебя уже есть статья про обработчик заявок с дедупликацией по телефону. А если интересно, почему повторы вообще вылезают, рядом лежит разбор про причины дублирования заявок.
Что улетает в Telegram
Я не люблю спартанские уведомления в духе “новая заявка, разберись сам”. Когда телега пиликает, у меня в сообщении уже должны лежать данные, с которыми можно работать. Минимум такой:
🆕 Новая заявка
Имя: {{$json.name}}
Телефон: {{$json.phone_normalized || $json.phone}}
Услуга: {{$json.service}}
Комментарий: {{$json.comment || '—'}}
Источник: {{$json.source || 'site-form'}}
Страница: {{$json.page || '—'}}
Если менеджеров несколько, есть два пути: слать в общий рабочий чат или сделать несколько Telegram-нoded с разными chat_id. Для маленькой команды я чаще кидаю всё в одну группу. Так меньше хаоса и легче отслеживать реакцию.
Ещё одна мелочь, которая потом радует глаз: в Telegram node можно отключить хвост с подписью n8n. Штука маленькая, а сообщение выглядит чище. Ну и само собой, сначала проверь, что бот вообще может писать в нужный чат. Поймать неверный chat_id на бою — история старая как гаечный ключ в ящике.
Если планируешь развивать эту схему дальше, логичное продолжение — либо складывать лиды в таблицу, либо сразу гонять их в CRM/Notion. На эту тему тебе подойдёт материал про обработку заявок через Google Sheets, а если хочется собрать ещё больше заготовок, глянь готовые шаблоны n8n.
Где схема обычно ломается
Я собрал тут самые частые фейлы, которые вижу у новичков и у себя самого, когда спешу и хочу всё поднять за вечер.
- В форме стоит test URL, а не production URL. В редакторе всё живо, на сайте пустота.
- Фронт шлёт GET, а webhook ждёт POST. Это вообще классика жанра.
- Кнопка не блокируется после клика, и человек создаёт несколько одинаковых отправок.
- Не настроен CORS, если форма и n8n живут на разных доменах.
- Ответ вебхука слишком долгий. Если процесс разросся, лучше быстро ответить форме, а тяжёлую обработку держать компактной и аккуратной.
- На сервере криво выставлены параметры домена и протокола, из-за чего webhook URL выглядит странно.
Я ещё советую держать под рукой отдельную схему контроля ошибок. Когда workflow уже вырос и в нём есть ветки, таблицы, уведомления и пара интеграций, полезно иметь контроль ошибок с retries и сигналами в Telegram. Тогда ты не гадаешь, почему часть заявок пропала, а видишь, где именно всё споткнулось.
Чек-лист перед публикацией
- Форма отправляет JSON через POST.
- Webhook активирован и в страницу подставлен production URL.
- Телефон и email нормализуются до дедупликации.
- Remove Duplicates сравнивает заявки по одному понятному ключу.
- Telegram-уведомление содержит все данные для первого контакта.
- Форма получает короткий и понятный ответ после отправки.
- Кнопка блокируется на время запроса.
- Тест дубля сделан вручную: одна и та же заявка не должна второй раз пиликнуть в Telegram.
Итог такой. Если тебе нужна рабочая форма заявки в n8n с кастомным HTML, я бы не мудрил: собственная HTML-страница, webhook, нормализация полей, Remove Duplicates, Telegram и короткий ответ пользователю. Схема простая, расширяется легко и уже на первом запуске спасает от бардака, когда лиды начинают падать быстрее, чем ты успеваешь их разбирать.