Как решить ошибку 403 в Telegraf.js при callback_query
Telegram разрешает сообщение в chat_join_request, но запрещает в callback_query, если пользователь не открыл чат с ботом. Как решить 403 в Telegraf.js через NGINX.
Как администратор группы в Telegram может взаимодействовать с новым пользователем, который отправил запрос на вступление, используя бота? После того как бот получает обновление chat_join_request, он отправляет сообщение с кнопками, но при обработке callback_query попытка отправить новое сообщение приводит к ошибке 403: Forbidden: bot can’t initiate conversation with a user. Почему Telegram позволяет отправлять сообщение в обработчике chat_join_request, но запрещает в обработчике callback_query, хотя chat_id одинаковый? Как решить эту проблему в Telegraf.js с webhook через NGINX?
Краткий ответ
Telegram позволяет отправлять сообщение в обработчике chat_join_request, потому что это «специальное» обновление, в котором бот уже считается «запрошенным» пользователем. В обработчике callback_query бот пытается инициировать приватный диалог с пользователем, которого пользователь ещё не начал в боте, и Telegram блокирует это как 403 Forbidden: bot can’t initiate conversation with a user. Чтобы решить проблему в Telegraf.js при работе через webhook, нужно убедиться, что пользователь уже открыл приватный чат с ботом (или заставить его открыть его через switch_pm) и использовать ctx.callbackQuery.from.id как chat_id при отправке сообщения.
1. Почему Telegram ведёт себя так
| Обработчик | Что делает Telegram | Почему сообщение разрешено / запрещено |
|---|---|---|
chat_join_request |
При поступлении запроса на вступление Telegram отправляет обновление с полем chatJoinRequest. Бот уже получает идентификатор пользователя (from.id) и может немедленно отправить ему сообщение в приватном чате. |
Telegram рассматривает это как «собственное» обновление от пользователя, поэтому разрешает инициировать диалог. |
callback_query |
Кнопка нажата в сообщении группы. Внутри callback_query содержится from.id, но это не приватный чат бота. Бот пытается отправить сообщение в приватный чат, который пользователь ещё не открыл. |
Telegram запрещает «инициировать» приватный чат без явного согласия пользователя. Поэтому вы получаете 403. |
Ключевой момент – в обоих случаях
chat_idодинаковый (идентификатор пользователя), но в первом случае Telegram позволяет, а во втором – нет, потому что в процессеcallback_queryпользователь ещё не начал разговор с ботом.
2. Как заставить пользователя открыть чат с ботом
2.1. switch_pm – открыть приватный чат из inline‑клавиатуры
bot.command('start', ctx => ctx.reply('Привет! Я бот.'));
bot.on('chat_join_request', ctx => {
const userId = ctx.chatJoinRequest.from.id;
// Отправляем пользователю сообщение с кнопкой «Открыть чат»
ctx.telegram.sendMessage(
userId,
'Присоединяйтесь к нам! Нажмите кнопку, чтобы открыть чат с ботом.',
{
reply_markup: {
inline_keyboard: [
[
{
text: 'Открыть чат',
switch_pm_text: 'Начать',
switch_pm_parameter: 'start',
},
],
],
},
}
);
});
switch_pm_parameter– произвольный параметр, который будет передан в/startпри открытии чата.
2.2. Проверка на наличие приватного чата
bot.on('callback_query', async ctx => {
const userId = ctx.callbackQuery.from.id;
try {
await ctx.telegram.sendMessage(userId, 'Ваше сообщение');
} catch (err) {
if (err.response && err.response.statusCode === 403) {
// Бот не может начать диалог
await ctx.reply(
'Пожалуйста, сначала откройте чат с ботом. Нажмите кнопку ниже.',
{
reply_markup: {
inline_keyboard: [
[
{
text: 'Открыть чат',
switch_pm_text: 'Начать',
switch_pm_parameter: 'start',
},
],
],
},
}
);
} else {
throw err;
}
}
});
Важно – использовать
ctx.telegram.sendMessage(userId, …)вместоctx.reply, потому чтоctx.replyпытается ответить в том же чате, где возникcallback_query(группе), а нам нужен приватный чат.
3. Настройка Telegraf.js + webhook через NGINX
3.1. Основной скрипт бота
const { Telegraf } = require('telegraf');
const bot = new Telegraf(process.env.BOT_TOKEN);
// обработчики, как в примерах выше
// ...
// Устанавливаем webhook
bot.telegram.setWebhook(`https://your.domain.com/${process.env.BOT_TOKEN}`).catch(console.error);
// Запускаем локальный HTTP‑сервер (порт 3000 по умолчанию)
bot.startWebhook(`/${process.env.BOT_TOKEN}`, null, 3000);
3.2. Конфигурация NGINX
server {
listen 80;
server_name your.domain.com;
location /${BOT_TOKEN} {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
# При желании можно добавить HTTPS
# ...
}
Пояснение – путь
/${BOT_TOKEN}должен совпадать с тем, что вы передаёте вsetWebhook. Если вы используете HTTPS, убедитесь, что сертификат валиден (Let’s Encrypt или аналог).
4. Итоги
- Почему различие –
chat_join_requestсчитается «специальным» обновлением, где Telegram разрешает отправку приватного сообщения, тогда какcallback_queryтребует, чтобы пользователь уже начал чат с ботом. - Как решить – заставить пользователя открыть чат через
switch_pm(илиswitch_inline_query), проверять наличие диалога и обрабатывать 403‑ошибку. - Как реализовать в Telegraf.js – использовать
ctx.telegram.sendMessage(userId, …)в обработчикеcallback_query, обрабатывать исключения и отвечать пользователю кнопкойswitch_pm. - Webhook через NGINX – настроить проксирование
location /<token>к локальному порту бота и установить webhook черезbot.telegram.setWebhook.
Эти шаги позволят администратору группы беспрепятственно взаимодействовать с новым пользователем, даже если он не инициировал диалог с ботом с самого начала.