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, через которого создаются и настраиваются все остальные боты. Он же выдаёт токен.

  1. Откройте Telegram и найдите в поиске @BotFather (у настоящего аккаунта стоит синяя галочка верификации).
  2. Нажмите Start — бот пришлёт список команд.
  3. Отправьте команду /newbot.
  4. BotFather спросит отображаемое имя (display name) — это то, что видят пользователи в шапке чата. Может быть любым, на любом языке: например, Заявки с сайта.
  5. Затем он спросит 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

Здесь у вас есть три варианта, куда бот будет присылать заявки. Разберём все.

Вариант А. Личный чат (проще всего)

  1. Найдите своего бота в поиске Telegram по его username.
  2. Откройте диалог и нажмите Start (это отправляет команду /start). Без этого шага бот не сможет вам написать.
  3. Напишите боту любое сообщение, например привет.
  4. Запросите обновления методом 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. Для личного чата этого достаточно.

Вариант Б. Группа (заявки видит вся команда)

Удобно, когда заявки должны видеть несколько человек и можно обсуждать их прямо под сообщением.

  1. Создайте группу в Telegram.
  2. Добавьте в неё своего бота как участника.
  3. Напишите в группе любое сообщение, начинающееся со слэша, например /start@my_site_leads_bot — так бот точно увидит его даже при включённом режиме приватности.
  4. Снова вызовите getUpdates и посмотрите chat.id.

У групп chat_id отрицательный: -4712345678. У супергрупп он начинается с -100: -1001234567890. Минус — часть идентификатора, его нужно передавать.

Важно про режим приватности. По умолчанию бот в группе видит только команды (/что-то), ответы на свои сообщения и упоминания. Это защита от чтения всей переписки. Для отправки уведомлений режим приватности не мешает — бот только пишет. Отключать его (/setprivacyDisable) нужно, только если бот должен читать все сообщения в группе.

Ещё нюанс: если обычная группа превращается в супергруппу (это происходит автоматически при включении истории, добавлении публичной ссылки и т.п.), её chat_id меняется. Тогда уведомления перестают приходить и нужно получить ID заново.

Вариант В. Канал

  1. Создайте канал.
  2. Добавьте бота в администраторы с правом публикации сообщений (обычным участником канала бот писать не может).
  3. Для публичного канала можно вместо числового ID указывать @имя_канала.
  4. Для приватного канала числовой 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_modeHTML или 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. Его алгоритм одинаков в любом стеке — меняются только имена функций:

  1. Проверить honeypot. Скрытое поле заполнено — молча вернуть «успех» и ничего не отправлять.
  2. Проверить CSRF-токен (в CMS и фреймворках это штатный механизм: nonce, csrf-token и т.п.).
  3. Проверить rate limit по IP: не чаще одной заявки в минуту. Хранилище — кэш, Redis или таблица в БД.
  4. Провалидировать поля на сервере: имя и сообщение не пустые, e-mail проходит проверку формата. При ошибке — ответ 422.
  5. Сохранить заявку у себя — в БД или письмом. Telegram может быть недоступен, а заявка теряться не должна.
  6. Собрать текст сообщения, экранируя каждое пользовательское значение и обрезая итог до 4096 символов.
  7. Отправить POST на sendMessage с таймаутом 10–15 секунд.
  8. Разобрать ответ: 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, если проектов несколько — заявки с разных сайтов будут лежать в разных ветках одной группы.
  • Не отправляйте в чат пароли и персональные данные сверх необходимого: история чата хранится у всех участников группы.

Чеклист внедрения

  1. Создал бота через /newbot у @BotFather, сохранил токен.
  2. Проверил токен методом getMe.
  3. Нажал /start в чате с ботом (или добавил бота в группу/канал).
  4. Получил chat_id через getUpdates.
  5. Отправил тестовое сообщение через sendMessage.
  6. Положил токен и chat_id в конфиг вне репозитория.
  7. Написал серверный обработчик формы: валидация → экранирование → отправка.
  8. Добавил honeypot, CSRF-токен и rate limit.
  9. Обработал ошибки: логи для себя, нейтральный текст для пользователя.
  10. Проверил длинный текст, спецсимволы < > & и повторную отправку.

Итог

Связка «форма на сайте → Telegram» состоит из трёх сущностей: бот, созданный через @BotFather (даёт токен), чат, в котором вы нажали /start или куда добавили бота (даёт chat_id), и один HTTP-запрос к методу sendMessage с вашего сервера. Язык и фреймворк здесь не важны — важно качество бэкенда: серверная валидация, экранирование пользовательского ввода под parse_mode, защита от спама, обработка ошибок и хранение токена вне клиента и вне репозитория. Сделайте эти вещи аккуратно — и уведомления будут приходить годами без вашего участия.