Проверка подписки на канал телеграм бот - стандартная задача для розыгрышей, закрытых веток обсуждения и платных материалов: бот должен закрыть доступ к контенту, пока человек не подписался на канал, и открыть его сразу после подписки. На практике это не пара строк в BotFather, а отдельная логика поверх Telegram Bot API, у которой есть нюансы с правами бота, приватными каналами и повторными проверками при отписке. Ниже - как это устроено изнутри и на что обращать внимание при реализации.
Как Telegram Bot API проверяет подписку на канал
За проверку отвечает метод getChatMember. Бот вызывает его с двумя параметрами: chat_id канала и user_id пользователя, а в ответ получает объект с полем status. Значения статуса такие: creator, administrator, member, restricted, left, kicked. Подписанным считается пользователь со статусом, отличным от left (вышел сам) и kicked (исключён или заблокирован). Никакого отдельного метода «проверить подписку» в API нет, это ровно один запрос getChatMember и сравнение статуса.
Важный момент: метод отдаёт актуальное состояние на момент запроса, кеша на стороне Telegram для бота нет. Если человек отписался секунду назад, следующий вызов getChatMember это увидит.
Права бота и настройка канала для проверки подписки
Чтобы getChatMember вообще отработал на канале, бота нужно добавить в администраторы этого канала. Для обычных пользователей канал показывает список подписчиков ограниченно, а бот без прав администратора получит ошибку доступа. Отдельных прав на публикацию постов или удаление сообщений не требуется, достаточно самого факта администраторского статуса.
Второй момент - формат chat_id. Для публичного канала с username можно передавать строку вида @channel_username. Для приватного канала username нет, и нужен числовой id вида -1001234567890. Получить его проще всего, переслав любое сообщение из канала боту с включённым режимом отладки, либо через вызов getChat, когда бот уже добавлен в администраторы.
Что проверить перед запуском
- Бот добавлен в администраторы канала (для группы достаточно быть участником, но для канала нужен именно админ-статус)
- chat_id захардкожен правильно: для приватных каналов это число с префиксом ‑100, а не username
- Ссылка на подписку ведёт на актуальный канал, а не на старый, если канал переименовывали или пересоздавали
Бесплатный материал
🎁 Полезный скрипт в подарок
Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.
Без спама. Отписка в 1 клик.
Проверка подписки в боте на aiogram: рабочий код
На aiogram 3.x проверка укладывается в одну асинхронную функцию и обработчик кнопки «Я подписался»:
from aiogram import Bot, F
from aiogram.types import CallbackQuery
from aiogram.exceptions import TelegramBadRequest
CHANNEL_ID = -1001234567890 # числовой chat_id приватного или публичного канала
async def is_subscribed(bot: Bot, user_id: int) -> bool:
try:
member = await bot.get_chat_member(chat_id=CHANNEL_ID, user_id=user_id)
except TelegramBadRequest:
return False
return member.status not in ("left", "kicked")
@dp.callback_query(F.data == "check_subscription")
async def check_subscription(call: CallbackQuery, bot: Bot):
if await is_subscribed(bot, call.from_user.id):
await call.message.edit_text("Подписка подтверждена, доступ открыт.")
else:
await call.answer("Подписки пока не вижу, попробуйте ещё раз через пару секунд", show_alert=True)
Поймать TelegramBadRequest обязательно: он прилетает и когда бот не админ канала, и когда пользователь никогда не запускал бота (тогда Telegram не может сопоставить user_id). Без этого перехвата бот просто упадёт на первом же неподписанном пользователе.
Проверка подписки на несколько каналов и чатов одновременно
Если условие - подписка сразу на несколько каналов или вступление в чат, логика та же, просто в цикле или через asyncio.gather для параллельных запросов:
import asyncio
CHANNELS = [-1001111111111, -1001222222222, "@public_channel_username"]
async def missing_channels(bot: Bot, user_id: int) -> list:
async def check(chat_id):
try:
member = await bot.get_chat_member(chat_id=chat_id, user_id=user_id)
return None if member.status not in ("left", "kicked") else chat_id
except TelegramBadRequest:
return chat_id
results = await asyncio.gather(*(check(c) for c in CHANNELS))
return [c for c in results if c]
Список missing_channels удобно использовать, чтобы показать пользователю не общее «подпишитесь на всё», а конкретно те каналы, где подписки не хватает, с отдельной кнопкой на каждый.
Приватные каналы, закрытые группы и ограничения API
С приватными каналами есть особенность: бот проверяет статус только у тех пользователей, кто хотя бы раз взаимодействовал с этим чатом на уровне Bot API (подписался, был приглашён, отправлял заявку). Проверить подписку человека, который вообще никогда не пересекался с ботом в контексте этого канала, нельзя в принципе, это ограничение самого API, а не бага реализации.
Для закрытых каналов с заявками на вступление (join request) логика немного другая: можно ловить событие chat_join_request и одобрять заявку из бота автоматически после выполнения условия, например после подтверждения email или прохождения капчи в самом боте. Это отдельный сценарий, который часто путают с обычной проверкой подписки, хотя механика на getChatMember там тоже используется, только на входе.
Таблица: публичный канал против приватного
| Параметр | Публичный канал | Приватный канал |
|---|---|---|
| Формат chat_id | @username или числовой id | только числовой id вида ‑100… |
| Как получить id | из ссылки на канал | через getChat или пересланное сообщение |
| Нужен ли бот в админах | да | да |
| Проверка подписки «незнакомого» пользователя | ограниченно доступна | недоступна до первого контакта с чатом |
Если логика выходит за рамки одного канала: несколько уровней подписки, тарифы, реферальные ссылки, привязка к CRM, проще сразу заказать разработку Telegram-бота с проверкой подписки под конкретный сценарий, чем дособирать это из кусков туториалов.
Частые ошибки при проверке подписки телеграм бота
- Бота не добавили в администраторы канала - getChatMember падает с ошибкой доступа даже для существующего публичного канала
- Хардкод username вместо числового id для приватного канала - при первом переименовании канала бот перестаёт находить чат
- Проверка выполняется один раз при первом запуске бота и больше никогда - пользователь отписывается и продолжает пользоваться доступом
- Статусы left и kicked не разделяются логически, хотя kicked обычно означает бан модератором и требует отдельной обработки, а не просто «предложить подписаться снова»
- Проверка дергается на каждое сообщение в чате без троттлинга, что при активной аудитории быстро упирается в лимиты Bot API на количество запросов в секунду
Сколько стоит настройка проверки подписки на канал
Если нужна проверка одного публичного канала перед выдачей ссылки на закрытый чат, это несколько часов работы вместе с обработкой edge-кейсов вроде повторной подписки, бана и приватных каналов. Разработка Telegram-бота у меня стартует от 30 000 ₽, в эту стоимость входит логика проверки подписки, работа с несколькими каналами при необходимости и деплой на сервер. На рынке за аналогичного бота студии и фрилансеры обычно просят 40 000-90 000 ₽, но там цена сильно зависит от того, входит ли в неё доработка под заявки на вступление, интеграция с CRM или просто разовая настройка одного условия.
Частые вопросы
Можно ли проверить подписку, если бот не добавлен в администраторы канала?
Нет. Для обычного пользователя (не администратора) Telegram Bot API не отдаёт статус участника канала, запрос getChatMember вернёт ошибку доступа. Бота нужно добавить в администраторы канала заранее, отдельных прав на публикацию для этого не требуется.
Как проверить подписку на приватный канал без username?
Используется числовой chat_id вида ‑100xxxxxxxxxx вместо @username. Его можно получить через метод getChat после добавления бота в администраторы, либо переслав боту любое сообщение из этого канала. Сама логика проверки через getChatMember не меняется.
Сколько раз можно проверять подписку без риска блокировки бота?
Ограничения Bot API считаются в запросах в секунду на бота (около 30 в общем потоке), а не в «проверках подписки в день». Проблема начинается, если проверка вызывается на каждое действие пользователя без троттлинга при большой аудитории. На практике достаточно проверять подписку по нажатию кнопки и один раз при повторном заходе в бота, а не на каждое сообщение.
Пользователь подписался, а бот всё равно показывает, что подписки нет, почему так?
Чаще всего дело в кеше на стороне самого бота (если результат сохраняли в базу и не обновляли) или в том, что проверка идёт по неверному chat_id, например по username вместо числового id для приватного канала. Сам Telegram Bot API отдаёт статус без задержки, так что если код обращается напрямую к getChatMember при каждой проверке, устаревших данных быть не должно.