Telegram-бот для уведомлений с сайта: BotFather, chat_id и отправка данных формы
В этой статье разберём весь путь от нуля до рабочего решения: как создать
Telegram-бота через @BotFather, что такое токен и
chat_id, как открыть чат с ботом (личный, групповой или канал),
и как подключить к сайту форму обратной связи так, чтобы каждая заявка
мгновенно приходила сообщением в Telegram. Разберём безопасность токена,
защиту от спама, лимиты Bot API и список типичных ошибок с расшифровкой.
Bot API — это обычный HTTPS-интерфейс, поэтому примеры даны в виде
HTTP-запросов (curl) и не привязаны к языку: то же самое
делается любым HTTP-клиентом — fetch, requests,
HttpClient, Net::HTTP и так далее. Логика везде
одинаковая: сформировать запрос, отправить, проверить поле ok
в JSON-ответе.
Как это работает в целом
Telegram предоставляет Bot API: вы делаете запрос на
api.telegram.org и получаете JSON. Никаких SDK и библиотек не
требуется, достаточно уметь отправить HTTP-запрос.
Схема работы формы на сайте выглядит так:
Браузер Ваш сервер Telegram
│ │ │
│ 1. Пользователь │ │
│ отправляет форму │ │
├───────── POST ────────────>│ │
│ │ 2. Валидация, антиспам │
│ │ │
│ │ 3. POST sendMessage │
│ │ (с секретным токеном) │
│ ├───────────────────────────>│
│ │ │
│ │<──── {"ok":true, ...} ─────┤
│ 4. Ответ «Спасибо» │ │
│<───────────────────────────┤ 5. Сообщение
│ │ в чате
Ключевой момент, который определяет всю архитектуру: запрос к Telegram делает сервер, а не браузер. Токен бота — это пароль к боту, и он не должен попадать в клиентский код, в HTML или в репозиторий. Об этом отдельный раздел ниже.
Два принципиальных ограничения ботов
-
Бот не может написать первым. Чтобы бот отправил вам
сообщение, вы должны сначала сами открыть с ним диалог и нажать
/start(или добавить бота в группу). Именно поэтому шаг «создать чат с ботом» обязателен. -
Бот пишет в конкретный чат по его идентификатору. Нужен
chat_id— число, которое однозначно определяет личный диалог, группу или канал. Без него отправить сообщение невозможно.
Шаг 1. Создание бота через @BotFather
@BotFather — это официальный бот Telegram, через которого создаются и настраиваются все остальные боты. Он же выдаёт токен.
- Откройте Telegram и найдите в поиске @BotFather (у настоящего аккаунта стоит синяя галочка верификации).
- Нажмите Start — бот пришлёт список команд.
- Отправьте команду
/newbot. - BotFather спросит отображаемое имя (display name) —
это то, что видят пользователи в шапке чата. Может быть любым, на любом
языке: например,
Заявки с сайта. - Затем он спросит username — уникальный логин бота. Он
обязан быть латиницей и заканчиваться на
bot(или_bot): например,my_site_leads_bot. Если имя занято — BotFather попросит другое.
После успешного создания BotFather пришлёт сообщение с токеном:
Done! Congratulations on your new bot.
Use this token to access the HTTP API:
<ID_БОТА>:<СЕКРЕТНАЯ_ЧАСТЬ>
Keep your token secure and store it safely.
Вместо <ID_БОТА>:<СЕКРЕТНАЯ_ЧАСТЬ> там будет
реальная строка: числовой идентификатор бота, двоеточие и примерно
35 символов случайного секрета. Дальше по тексту токен всегда обозначен как
<ТОКЕН> — подставляйте вместо него свой и никогда не
публикуйте его в открытом виде. Это единственный ключ
доступа: кто им владеет, тот полностью управляет ботом.
Полезные команды BotFather
/mybots— список ваших ботов и меню настроек через кнопки;/token— показать токен ещё раз;/revoke— отозвать токен и выпустить новый (делайте это сразу, если токен утёк — старый мгновенно перестаёт работать);/setname— сменить отображаемое имя;/setdescription— описание, которое видно в пустом чате до/start;/setabouttext— короткий текст в профиле бота;/setuserpic— аватар;/setcommands— список команд, который показывается в меню чата;/setprivacy— режим приватности в группах (см. раздел про группы);/deletebot— удалить бота.
Для сценария «уведомления с сайта» достаточно создать бота и получить токен — остальное косметика.
Шаг 2. Проверяем токен
Все методы Bot API вызываются по единому шаблону URL:
https://api.telegram.org/bot<ТОКЕН>/<МЕТОД>
Обратите внимание: слово bot пишется слитно с токеном. Самый
простой метод для проверки — getMe:
curl https://api.telegram.org/bot<ТОКЕН>/getMe
Ответ:
{
"ok": true,
"result": {
"id": 1234567890,
"is_bot": true,
"first_name": "Заявки с сайта",
"username": "my_site_leads_bot",
"can_join_groups": true,
"can_read_all_group_messages": false,
"supports_inline_queries": false
}
}
Если пришло {"ok":false,"error_code":401,"description":"Unauthorized"} —
токен скопирован неверно или отозван.
Шаг 3. Создаём чат с ботом и получаем chat_id
Здесь у вас есть три варианта, куда бот будет присылать заявки. Разберём все.
Вариант А. Личный чат (проще всего)
- Найдите своего бота в поиске Telegram по его username.
- Откройте диалог и нажмите Start (это отправляет команду
/start). Без этого шага бот не сможет вам написать. - Напишите боту любое сообщение, например
привет. - Запросите обновления методом
getUpdates:
curl https://api.telegram.org/bot<ТОКЕН>/getUpdates
В ответе найдите блок chat:
{
"ok": true,
"result": [
{
"update_id": 100000001,
"message": {
"message_id": 3,
"from": { "id": 384756123, "first_name": "Иван", "username": "ivan" },
"chat": {
"id": 384756123,
"first_name": "Иван",
"username": "ivan",
"type": "private"
},
"date": 1755300000,
"text": "привет"
}
}
]
}
chat.id (здесь 384756123) — это и есть ваш
chat_id. У личных чатов он положительный и совпадает с ID
пользователя. В отличие от токена, chat_id не секрет: сам по
себе, без токена, он ничего не даёт.
Альтернатива без консоли: написать боту @userinfobot — он ответит вашим числовым ID. Для личного чата этого достаточно.
Вариант Б. Группа (заявки видит вся команда)
Удобно, когда заявки должны видеть несколько человек и можно обсуждать их прямо под сообщением.
- Создайте группу в Telegram.
- Добавьте в неё своего бота как участника.
- Напишите в группе любое сообщение, начинающееся со слэша, например
/start@my_site_leads_bot— так бот точно увидит его даже при включённом режиме приватности. - Снова вызовите
getUpdatesи посмотритеchat.id.
У групп chat_id отрицательный:
-4712345678. У супергрупп он начинается с -100:
-1001234567890. Минус — часть идентификатора, его нужно
передавать.
Важно про режим приватности. По умолчанию бот в группе
видит только команды (/что-то), ответы на свои сообщения и
упоминания. Это защита от чтения всей переписки. Для отправки уведомлений
режим приватности не мешает — бот только пишет. Отключать его
(/setprivacy → Disable) нужно, только если бот
должен читать все сообщения в группе.
Ещё нюанс: если обычная группа превращается в супергруппу
(это происходит автоматически при включении истории, добавлении публичной
ссылки и т.п.), её chat_id меняется. Тогда уведомления перестают
приходить и нужно получить ID заново.
Вариант В. Канал
- Создайте канал.
- Добавьте бота в администраторы с правом публикации сообщений (обычным участником канала бот писать не может).
- Для публичного канала можно вместо числового ID указывать
@имя_канала. - Для приватного канала числовой ID удобно получить, переслав любое
сообщение из канала боту @getmyid_bot, либо через
getUpdates— там придёт объектchannel_post.
Если getUpdates возвращает пустой результат
- Вы не написали боту сообщение после
/start— напишите. - Обновления уже были «прочитаны» другим запросом. Telegram отдаёт каждое обновление один раз при подтверждении; просто напишите новое сообщение боту.
- Ошибка
409 Conflict: terminated by other getUpdates requestили установлен вебхук. Снимите вебхук и повторите:
curl https://api.telegram.org/bot<ТОКЕН>/deleteWebhook
Шаг 4. Первое сообщение от бота
Метод sendMessage — основной для уведомлений:
curl -X POST https://api.telegram.org/bot<ТОКЕН>/sendMessage \
-d chat_id=384756123 \
-d text="Проверка связи"
Telegram принимает параметры и как application/x-www-form-urlencoded,
и как JSON — второй вариант обычно удобнее вызывать из кода:
curl -X POST https://api.telegram.org/bot<ТОКЕН>/sendMessage \
-H "Content-Type: application/json" \
-d '{"chat_id":"384756123","text":"Проверка связи","parse_mode":"HTML"}'
Если в чате появилось сообщение — вся связка работает, дальше остаётся только выполнить этот же запрос из кода сайта.
Полезные параметры sendMessage
chat_id— обязательный: ID чата или@usernameпубличного канала;text— обязательный: текст, максимум 4096 символов;parse_mode—HTMLилиMarkdownV2для форматирования;link_preview_options— например{"is_disabled":true}, чтобы не разворачивались превью ссылок (устаревший аналог —disable_web_page_preview=true);disable_notification— прислать без звука;protect_content— запретить пересылку и копирование;message_thread_id— ID темы, если группа переведена в режим форума с темами;reply_markup— inline-кнопки (например, «Позвонить», «Открыть CRM»).
Форматирование: используйте HTML
Из двух режимов разметки HTML удобнее: в MarkdownV2
требуется экранировать полтора десятка спецсимволов, и любой символ
-, . или ! в тексте пользователя
ломает сообщение. В режиме HTML поддерживается ограниченный
набор тегов:
<b>жирный</b>, <i>курсив</i>, <u>подчёркнутый</u>, <s>зачёркнутый</s>
<a href="https://example.com">ссылка</a>
<code>моноширинный</code>, <pre>блок кода</pre>
<blockquote>цитата</blockquote>, <tg-spoiler>спойлер</tg-spoiler>
Критично: все данные, пришедшие от пользователя, нужно
экранировать перед подстановкой в такой текст, иначе символ <
в сообщении посетителя вызовет ошибку
400 Bad Request: can't parse entities — и заявка не дойдёт.
Экранирование здесь ровно то же, что при выводе в веб-страницу: заменить
&, < и > на HTML-сущности.
В каждом языке для этого есть штатная функция.
Шаг 5. Где хранить токен
Токен — это секрет уровня пароля от базы данных. Правила простые:
- Никогда не помещайте токен в клиентский код, в HTML или в публичный запрос — любой откроет DevTools и заберёт его.
- Не коммитьте токен в git. Даже удалённый позже коммит остаётся в истории.
- Храните в переменных окружения, в менеджере секретов хостинга или в конфиге, который лежит вне репозитория и вне публичной директории.
- Разделяйте окружения: у прода и у разработки — разные боты и разные токены.
- Если токен всё-таки утёк — сразу
/revokeу BotFather.
Типовой вариант — файл .env, добавленный в .gitignore:
TELEGRAM_BOT_TOKEN=<ТОКЕН>
TELEGRAM_CHAT_ID=-1001234567890
В коде значения читаются из окружения — на любом языке это одна строка. Если фреймворк или CMS предлагает собственное место для констант и настроек вне репозитория, используйте его.
Шаг 6. Форма на сайте: серверный обработчик
Разметка формы — обычная, без единого упоминания Telegram:
<form action="/send" method="post">
<input type="text" name="name" placeholder="Имя" required>
<input type="email" name="email" placeholder="E-mail" required>
<textarea name="message" placeholder="Сообщение" required></textarea>
<!-- honeypot: настоящий пользователь его не видит и не заполняет -->
<input type="text" name="website" tabindex="-1" autocomplete="off"
style="position:absolute;left:-9999px">
<button type="submit">Отправить</button>
</form>
Дальше работает серверный обработчик по адресу /send. Его
алгоритм одинаков в любом стеке — меняются только имена функций:
- Проверить honeypot. Скрытое поле заполнено — молча вернуть «успех» и ничего не отправлять.
- Проверить CSRF-токен (в CMS и фреймворках это штатный механизм: nonce, csrf-token и т.п.).
- Проверить rate limit по IP: не чаще одной заявки в минуту. Хранилище — кэш, Redis или таблица в БД.
- Провалидировать поля на сервере: имя и сообщение не
пустые, e-mail проходит проверку формата. При ошибке — ответ
422. - Сохранить заявку у себя — в БД или письмом. Telegram может быть недоступен, а заявка теряться не должна.
- Собрать текст сообщения, экранируя каждое пользовательское значение и обрезая итог до 4096 символов.
- Отправить POST на
sendMessageс таймаутом 10–15 секунд. - Разобрать ответ:
ok: true— успех; иначе записатьdescriptionв лог и вернуть пользователю нейтральное сообщение об ошибке.
Тело запроса к Telegram — обычный JSON:
POST https://api.telegram.org/bot<ТОКЕН>/sendMessage
Content-Type: application/json
{
"chat_id": "-1001234567890",
"parse_mode": "HTML",
"link_preview_options": { "is_disabled": true },
"text": "<b>Новая заявка с сайта</b>\n\n<b>Имя:</b> Иван\n<b>E-mail:</b> ivan@example.com\n<b>Сообщение:</b>\nНужен лендинг\n\n<i>Страница:</i> https://example.com/contacts"
}
Успешный ответ:
{ "ok": true, "result": { "message_id": 42, "date": 1755300000, "text": "..." } }
Ответ с ошибкой:
{ "ok": false, "error_code": 400, "description": "Bad Request: chat not found" }
Проверять нужно именно поле ok, а не только HTTP-код: удобнее
один раз написать функцию-обёртку «отправить сообщение → вернуть успех или
текст ошибки» и вызывать её из всех форм проекта.
На фронтенде добавьте блокировку кнопки на время запроса. Это не косметика: без неё нетерпеливый пользователь отправит форму пять раз, и в чат придёт пять одинаковых заявок.
И главное про валидацию: атрибут required в HTML — подсказка
пользователю, а не защита. Запрос легко отправить напрямую, минуя вашу
форму, поэтому все проверки обязаны повторяться на сервере.
Отправка файлов из формы
Если в форме есть загрузка файла (резюме, ТЗ, скриншот), используйте
sendDocument вместо sendMessage. Запрос
отправляется как multipart/form-data, где файл идёт в поле
document:
curl -X POST https://api.telegram.org/bot<ТОКЕН>/sendDocument \
-F chat_id=-1001234567890 \
-F caption="Файл к заявке от Ивана" \
-F document=@/tmp/upload.pdf
Ограничение Bot API — до 50 МБ на отправляемый файл, подпись
(caption) — до 1024 символов. Обязательно проверяйте на сервере
размер, MIME-тип и расширение: пересылать в чат произвольные файлы из
интернета — плохая идея. Для картинок есть sendPhoto, для
нескольких файлов сразу — sendMediaGroup.
Лимиты Bot API
- до 30 сообщений в секунду суммарно по всем чатам;
- примерно 1 сообщение в секунду в один и тот же чат (короткие всплески допускаются);
- не более 20 сообщений в минуту в одну группу или канал;
- текст сообщения — 4096 символов, подпись к файлу — 1024;
- при превышении приходит
429 Too Many Requestsи полеparameters.retry_afterс числом секунд, через которое можно повторить.
Для формы обратной связи эти лимиты недостижимы при нормальном трафике, но легко упереться при спам-атаке — ещё одна причина ставить rate limit на своей стороне.
Защита формы от спама
Минимальный набор, который отсекает большинство ботов:
- Honeypot — скрытое поле, которое видят только боты. Заполнено — тихо отбрасываем запрос, возвращая «успех», чтобы спамер не понял логику.
- Проверка времени. Кладём в скрытое поле timestamp отрисовки формы; отправка быстрее 2–3 секунд после загрузки — почти наверняка бот.
- Rate limit по IP — через кэш, Redis или запись в БД.
- CSRF-токен — чтобы форму нельзя было отправлять со стороннего сайта.
- Капча (Cloudflare Turnstile, hCaptcha, reCAPTCHA) — если предыдущего не хватило.
- Серверная валидация всегда, независимо от того, что проверяется в браузере.
Если нужно принимать ответы: getUpdates против webhook
Для одностороннего сценария «сайт → Telegram» всё описанное выше уже достаточно: сервер сам инициирует запрос. Но если бот должен реагировать (принимать команды, вести диалог, показывать кнопки), нужно получать входящие обновления. Есть два способа.
-
Long polling (
getUpdates) — ваш скрипт сам периодически спрашивает Telegram: «есть что-нибудь новое?». Работает откуда угодно, даже с локальной машины без белого IP, но требует постоянно запущенного процесса. - Webhook — Telegram сам делает POST-запрос на ваш URL при каждом событии. Быстрее и дешевле, но нужен публичный HTTPS-адрес с валидным сертификатом на порту 443, 80, 88 или 8443.
Установка вебхука с секретом:
curl -X POST https://api.telegram.org/bot<ТОКЕН>/setWebhook \
-d url=https://example.com/api/telegram/webhook \
-d secret_token=длинная_случайная_строка
Telegram будет присылать этот секрет в заголовке
X-Telegram-Bot-Api-Secret-Token — сверяйте его в обработчике,
иначе ваш эндпоинт сможет дёрнуть кто угодно. Проверить состояние вебхука и
увидеть последнюю ошибку доставки:
curl https://api.telegram.org/bot<ТОКЕН>/getWebhookInfo
Важно: getUpdates и webhook взаимоисключающие. Пока
установлен вебхук, getUpdates будет возвращать ошибку
409 Conflict.
Типичные ошибки и что они значат
-
401 Unauthorized — неверный или отозванный токен.
Проверьте, что в URL после
botнет пробела, кавычек и переносов строки. -
400 Bad Request: chat not found — неверный
chat_id, потерян минус у группы, либо пользователь ни разу не нажимал/start. - 403 Forbidden: bot was blocked by the user — пользователь заблокировал бота. Уведомления ему больше не дойдут.
- 403 Forbidden: bot is not a member of the channel chat — бота не добавили в канал или не дали права администратора.
-
400 Bad Request: can't parse entities — сломанная
разметка при
parse_mode. В 99% случаев причина — неэкранированный пользовательский ввод. Для диагностики уберитеparse_mode: если сообщение уходит — дело точно в экранировании. - 400 Bad Request: message is too long — превышены 4096 символов, обрежьте текст.
-
429 Too Many Requests — упёрлись в лимит, ждите
retry_afterсекунд. -
Локально ничего не отправляется, а на проде работает —
в локальных сборках часто нет актуальных корневых сертификатов, и запрос
падает на проверке TLS. Правильное решение — прописать свежий
cacert.pemв настройках HTTP-клиента или среды, а не отключать проверку сертификата. -
Таймаут при обращении к api.telegram.org — сервер
хостинга не может достучаться до Telegram (фаервол, блокировки,
закрытый исходящий трафик). Проверьте с сервера:
curl -v https://api.telegram.org. Если доступа нет — понадобится прокси или другой хостинг.
Практические советы по эксплуатации
- Дублируйте заявку на почту или в БД. Telegram может быть недоступен, а заявка — это деньги. Сначала сохраните её у себя, потом отправляйте уведомление.
- Никогда не показывайте пользователю ошибку Telegram. Текст вроде «chat not found» ничего ему не говорит и намекает на внутреннее устройство. Пишите нейтральное сообщение, а детали — в лог.
- Добавьте контекст в сообщение: URL страницы, UTM-метки, источник, время. Через месяц это сильно упростит разбор заявок.
- Заведите отдельного бота для разработки и отдельный тестовый чат, чтобы отладочные заявки не сыпались в рабочую группу.
-
Используйте темы (topics) в супергруппе и параметр
message_thread_id, если проектов несколько — заявки с разных сайтов будут лежать в разных ветках одной группы. - Не отправляйте в чат пароли и персональные данные сверх необходимого: история чата хранится у всех участников группы.
Чеклист внедрения
- Создал бота через
/newbotу @BotFather, сохранил токен. - Проверил токен методом
getMe. - Нажал
/startв чате с ботом (или добавил бота в группу/канал). - Получил
chat_idчерезgetUpdates. - Отправил тестовое сообщение через
sendMessage. - Положил токен и
chat_idв конфиг вне репозитория. - Написал серверный обработчик формы: валидация → экранирование → отправка.
- Добавил honeypot, CSRF-токен и rate limit.
- Обработал ошибки: логи для себя, нейтральный текст для пользователя.
- Проверил длинный текст, спецсимволы
< > &и повторную отправку.
Итог
Связка «форма на сайте → Telegram» состоит из трёх сущностей: бот, созданный
через @BotFather (даёт токен), чат, в котором вы нажали
/start или куда добавили бота (даёт chat_id), и
один HTTP-запрос к методу sendMessage с вашего сервера. Язык и
фреймворк здесь не важны — важно качество бэкенда: серверная валидация,
экранирование пользовательского ввода под parse_mode, защита от
спама, обработка ошибок и хранение токена вне клиента и вне репозитория.
Сделайте эти вещи аккуратно — и уведомления будут приходить годами
без вашего участия.