Разработка Telegram-ботов на Node.js давно вийшла за межі простих скриптів-автовідповідачів. Сучасні бізнес-рішення вимагають надійної обробки транзакцій, стійкості до високих навантажень, безпечної роботи з вебхуками та гнучкого ведення діалогів (FSM). Популярна раніше бібліотека node-telegram-bot-api морально застаріла. Сьогодні стандартом індустрії для JavaScript/TypeScript розробників стає фреймворк grammY.
У цьому посібнику ми створимо готового до продакшену Telegram-бота на Node.js. Ми реалізуємо безпечну обробку реферальних посилань (Deep Linking), побудуємо лінійний діалог збору контактів клієнта з генерацією унікального ID заявки, налаштуємо Express-сервер для Webhook з валідацією секретного токена та розберем, як не впасти при отриманні лімітів HTTP 429.
Ініціалізація проєкту та безпечна обробка Deep Linking
Почнемо з ініціалізації проєкту та базового налаштування. Для роботи нам знадобляться пакети grammy та dotenv для безпечного зберігання токена бота у змінних середовища.
При першому запуску бота користувачем часто потрібно передати контекст — наприклад, ID партнера або промокод. Для цього використовується механізм Deep Linking: посилання формату t.me/YourBot?start=payload. При переході за таким посиланням Telegram надсилає команду /start з параметром у текстовому полі. Наше завдання — безпечно витягти та валідувати цей параметр.
import { Bot } from "grammy";
import dotenv from "dotenv";
dotenv.config();
const token = process.env.BOT_TOKEN;
if (!token) {
throw new Error("BOT_TOKEN не задан в переменных окружения!");
}
// Инициализация бота
const bot = new Bot(token);
// Обработка команды /start с поддержкой deep-linking
bot.command("start", async (ctx) => {
const payload = ctx.match; // Содержит строку после ?start=
if (payload) {
// Валидируем входящие данные: разрешаем только латиницу, цифры и дефис
const cleanPayload = payload.replace(/[^a-zA-Z0-9_-]/g, "");
if (cleanPayload.length > 64) {
return ctx.reply("Ошибка: Некорректный реферальный код.");
}
await ctx.reply(`Добро пожаловать! Вы перешли по партнерской ссылке: ${cleanPayload}`);
} else {
await ctx.reply("Приветствуем! Нажмите /register, чтобы оставить заявку на обслуживание.");
}
});
// Запуск в режиме Long Polling (для локальной разработки)
if (process.env.NODE_ENV !== "production") {
bot.start();
console.log("Бот запущен локально через Long Polling");
}Покроковий збір лідів із плагіном Conversations
Для реалізації покрокових сценаріїв (наприклад, анкетування або запису на послуги) в grammY використовується потужний плагін @grammyjs/conversations. На відміну від класичних скінченних автоматів (FSM), де доводиться вручную зберігати крок користувача в базу даних і писати десятки розгалужень, плагін дозволяє писати послідовний асинхронний код з використанням await conversation.waitFor().
Перед тим як почати діалог, налаштуємо вбудоване сховище сесій. У реальному продакшені рекомендується замінити дефолтное сховище в оперативній пам'яті (In-Memory) на адаптер Redis, щоб сесії не скидалися при перезапуску Node.js процесу.
Також врахуйте важливе обмеження Telegram API: розмір параметра callback_data в інлайн-кнопках не повинен перевищувати 64 байти. Передавати туди складні структури даних не можна — генеруйте короткі хеші та зв'язуйте їх із сутностями в БД.
import { session } from "grammy";
import { conversations, createConversation } from "@grammyjs/conversations";
import crypto from "crypto";
// Настройка middleware сессий
bot.use(session({ initial: () => ({}) }));
bot.use(conversations());
/**
* Сценарий сбора контактных данных лида
*/
async function collectLeadConversation(conversation, ctx) {
await ctx.reply("Пожалуйста, введите ваше имя и фамилию:");
// Ожидаем текстовое сообщение от пользователя
const nameCtx = await conversation.waitFor("message:text");
const clientName = nameCtx.message.text.trim();
await ctx.reply("Отлично! Теперь отправьте ваш номер телефона для связи:");
const phoneCtx = await conversation.waitFor("message:text");
const clientPhone = phoneCtx.message.text.trim();
// Генерация криптографически стойкого ID лида
const leadId = crypto.randomBytes(7).toString("hex");
// Сохраняем лид во внешнюю БД (имитация через conversation.external)
await conversation.external(async () => {
// В реальном проекте здесь будет: await db.leads.insert({ id: leadId, name: clientName, phone: clientPhone })
console.log(`[DB] Создан лид #${leadId}: ${clientName} (${clientPhone})`);
});
await ctx.reply(`Спасибо! Ваша заявка #${leadId} успешно зарегистрирована. Наш менеджер свяжется с вами.`);
}
// Регистрация диалога во фреймворке
bot.use(createConversation(collectLeadConversation));
// Команда для запуска диалога
bot.command("register", async (ctx) => {
await ctx.conversation.enter("collectLeadConversation");
});Продакшн-розгортання: Webhooks з валідацією Secret Token
Для локальної розробки чудово підходить Long Polling (метод getUpdates), але в продакшені під високими навантаженнями обов'язковим стандартом є використання Webhooks. Вебхуки знижують затримку відповідей бота та економять ресурси сервера, оскільки Telegram сам надсилає події (Updates) на ваш HTTPS-сервер.
Для захисту вашого ендпоїнту від сторонніх запитів Telegram дозволяє задати секретний токен при реєстрації вебхука (параметр secret_token у методі setWebhook). Telegram надсилатиме цей токен в HTTP-заголовку X-Telegram-Bot-API-Secret-Token при кожному запиті. Наш Node.js сервер зобов'язаний перевіряти цей заголовок.
Нижче наведено приклад інтеграції бота з веб-сервером Express:
import express from "express";
import { webhookCallback } from "grammy";
if (process.env.NODE_ENV === "production") {
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 3000;
const SECRET_TOKEN = process.env.TELEGRAM_SECRET_TOKEN;
if (!SECRET_TOKEN) {
throw new Error("Критическая ошибка: TELEGRAM_SECRET_TOKEN не задан!");
}
// Middleware для проверки подлинности запросов от Telegram
const verifyTelegramSecret = (req, res, next) => {
const receivedToken = req.headers["x-telegram-bot-api-secret-token"];
if (receivedToken !== SECRET_TOKEN) {
console.warn("Попытка несанкционированного доступа к вебхуку!");
return res.status(403).send("Forbidden");
}
next();
};
// Роут вебхука
app.post(
"/telegram-webhook",
verifyTelegramSecret,
webhookCallback(bot, "express")
);
app.listen(PORT, () => {
console.log(`Сервер вебхуков запущен на порту ${PORT}`);
});
}Пастка лімітів: Боротьба з помилками HTTP 429 (Too Many Requests)
При масштабуванні бота ви неминуче зіткнетеся з жорсткими лімітами Telegram Bot API:
- Не більше 30 повідомлень на секунду сумарно в усі чати;
- Не більше 1 повідомлення на секунду в конкретний приватний чат;
- Не більше 20 повідомлень на хвилину в одну групу/супергрупу.
Якщо ваш додаток перевищує ці показники (наприклад, при масовій розсилці повідомлень про статус замовлення або акції), Telegram повертає помилку HTTP 429 Too Many Requests з параметром retry_after у тілі відповіді. Якщо ігнорувати цей статус і продовжувати спамить API, Telegram тимчасово заблокує токен вашого бота.
Для обходу цієї проблеми в екосистемі grammY існує офіційний плагін автоповторів запитів — @grammyjs/auto-retry. Він перехоплює помилки 429, ставить запити на паузу на вказаний сервером час і автоматично повторює їх.
import { autoRetry } from "@grammyjs/auto-retry";
// Подключаем плагин автоповтора к API-клиенту бота
bot.api.config.use(
autoRetry({
maxDelaySeconds: 30, // Максимальное время ожидания перед повторной попыткой
maxRetryAttempts: 3, // Количество попыток отправить запрос повторно
})
);Використання плагіна гарантує, що ваші користувачі гарантовано отримають свої сповіщення, а Node.js процес не впаде через необроблений виняток у промісі.
Висновок
Ми створили надійний скелет Telegram-бота на Node.js з використанням бібліотеки grammY. Ми навчилися безпечно приймати ліди через лінійні діалоги, захистили наш Webhook-ендпоїнт за допомогою секретного токена та впровадили механізм автоматичного обходу лімітів API. Ця архітектура готова до інтеграції з CRM-системами та базами даних у реальному бізнесі.
Якщо вам потрібна розробка професійного та відмовостійкого Telegram-бота під ключ, зверніться до фахівців компанії BotCreator.