Разработка · 6 мин чтения

Проверка подписки на канал телеграм бот: как устроен механизм

Проверка подписки на канал телеграм бот - стандартная задача для розыгрышей, закрытых веток обсуждения и платных материалов: бот должен закрыть доступ к контенту, пока человек не подписался на канал, и открыть его сразу после подписки. На практике это не пара строк в 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 при каждой проверке, устаревших данных быть не должно.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно