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

Бот для Telegram с подпиской на канал: как сделать проверку через API

Бот для Telegram с подпиской на канал - типовая задача для закрытых каналов, марафонов, розыгрышей и продажи доступа к контенту. Я собирал такую логику десятки раз для разных клиентов: от простого гейта «подпишись - получи чек-лист» до сложных сценариев с несколькими каналами и закрытыми группами. Разберу, как это устроено на уровне Bot API, и покажу рабочий код на aiogram, который можно брать за основу.

Зачем проверять подписку на канал в боте

Самый частый кейс из моей практики - лид-магнит. Пользователь пишет боту /start, получает предложение подписаться на канал, после подписки бот выдаёт PDF-гайд, промокод или доступ к закрытой части контента. Без проверки часть аудитории подписывается, забирает бонус и через день отписывается - метрика удержания канала превращается в пыль.

Второй кейс - розыгрыши и марафоны, где организатор обязан подтвердить, что участник реально состоит в канале, а не просто нажал кнопку «участвую». Третий - платные закрытые каналы, где бот выполняет роль привратника: без активной подписки не открывает доступ к материалам или следующему уроку.

Во всех этих сценариях логика одна: бот дергает Telegram Bot API, получает статус пользователя в канале и на основе этого статуса решает, показывать контент или отправлять на подписку.

Метод getChatMember и статусы участников канала

Проверка держится на одном методе Bot API - getChatMember. Он принимает chat_id канала (username вида @channel или числовой id, если канал приватный) и user_id проверяемого пользователя, а возвращает объект ChatMember со статусом.

Статусы, с которыми реально приходится работать:

Статус Что значит
creator владелец канала
administrator админ канала
member обычный подписчик
restricted участник с ограничениями (актуально для групп)
left вышел или не подписан
kicked заблокирован в канале

Подписанным я в коде считаю пользователя со статусом creator, administrator или member. Всё остальное - доступ закрыт.

Обязательное условие, о которое спотыкаются почти все новички: бот должен быть добавлен в канал администратором. Без этого getChatMember для приватного канала вернёт ошибку доступа, а для публичного может отработать нестабильно в зависимости от настроек канала.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Проверка подписки в боте на aiogram: рабочий пример

Показываю кусок кода на aiogram 3.x - так я обычно закладываю проверку в новых ботах для клиентов.

from aiogram import Bot, Dispatcher, F
from aiogram.filters import CommandStart
from aiogram.types import (
    Message, CallbackQuery,
    InlineKeyboardMarkup, InlineKeyboardButton,
)

CHANNEL_ID = "@kalinkindev_channel"


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)
        return member.status in ("member", "administrator", "creator")
    except Exception:
        return False


def subscribe_keyboard() -> InlineKeyboardMarkup:
    return InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="Подписаться", url="https://t.me/kalinkindev_channel")],
        [InlineKeyboardButton(text="Я подписался", callback_data="check_sub")],
    ])


async def start_handler(message: Message, bot: Bot):
    if await is_subscribed(bot, message.from_user.id):
        await message.answer("Доступ открыт, добро пожаловать!")
    else:
        await message.answer(
            "Чтобы продолжить, подпишитесь на канал",
            reply_markup=subscribe_keyboard(),
        )


async def check_sub_handler(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)


dp = Dispatcher()
dp.message.register(start_handler, CommandStart())
dp.callback_query.register(check_sub_handler, F.data == "check_sub")

Функция is_subscribed - это и есть вся логика проверки. Дальше её можно вызывать в любом хендлере: перед выдачей файла, перед показом меню, в middleware для всего бота сразу.

Готовый каркас с такой проверкой и парой смежных фич я выкладываю в библиотеке готовых скриптов - можно забрать как отправную точку и не собирать структуру бота с нуля.

Кнопка «Я подписался» и повторная проверка без перезапуска

Плохой UX - заставлять пользователя заново набирать /start после подписки. Правильно - кнопка «Я подписался» с callback_data, которая триггерит повторный вызов getChatMember и меняет текст того же сообщения через edit_text, без нового сообщения в чате.

Если проверка не прошла, я не переписываю сообщение целиком, а показываю всплывающее уведомление через call.answer(text, show_alert=True) - так пользователь видит, что бот отреагировал на нажатие, а исходное сообщение с кнопками остаётся на месте.

Отдельный момент - защита от спам-кликов. Если пользователь долбит кнопку раз в секунду, каждый клик - это отдельный запрос к API. На проектах с большой активной аудиторией я ставлю простой троттлинг: храню в памяти (или в Redis, если инстансов бота несколько) время последней проверки конкретного пользователя и игнорирую повторные клики чаще, чем раз в 3-5 секунд.

Ограничения API и типичные ошибки проверки подписки

С чем реально сталкиваешься на проде:

  • Bad Request: user not found - пользователь никогда не взаимодействовал с ботом или каналом, Telegram ещё не «видит» его в контексте этого чата. Актуально в основном для сценариев, где id пользователя пробуют получить из внешнего источника, а не из апдейта самого бота.
  • Forbidden: bot is not a member of the channel - бота удалили из админов канала после запуска. Проверяю это на старте бота и логирую отдельно, чтобы не тратить время на дебаг основной логики.
  • Лимиты API - Telegram ограничивает бота примерно 30 запросами в секунду суммарно и 1 сообщением в секунду на один чат. getChatMember считается в общий лимит, и на аудитории от 10-15 тысяч человек прямая проверка при каждом действии пользователя быстро упрётся в троттлинг со стороны Telegram.

Для крупных ботов я кэширую результат проверки на 5-10 минут (обычный словарь с TTL или Redis) и полагаюсь на него между реальными обращениями к API. Для событий в реальном времени есть альтернатива - подписка на апдейты chat_member, которые Telegram присылает боту сам при вступлении или выходе пользователя из канала, без необходимости дергать getChatMember вручную по каждому действию.

Способ Когда срабатывает Нагрузка на API
Прямой вызов getChatMember по клику сразу, по требованию пользователя высокая при большой аудитории
Кэш с TTL 5-10 минут с задержкой до истечения кэша низкая
Подписка на chat_member апдейты в момент реального события в канале минимальная, без опроса

Проверка нескольких каналов, закрытых групп и автоматизация в n8n

Если каналов несколько, я просто прогоняю пользователя по списку и требую member-статус в каждом:

CHANNELS = ["@kalinkindev_channel", "@kalinkindev_chat"]


async def is_subscribed_all(bot: Bot, user_id: int) -> bool:
    for chat_id in CHANNELS:
        member = await bot.get_chat_member(chat_id=chat_id, user_id=user_id)
        if member.status not in ("member", "administrator", "creator"):
            return False
    return True

С закрытыми группами метод работает точно так же, важно только заранее знать числовой chat_id - у приватных групп и каналов нет публичного username, id придётся получить через getChat или из апдейта, когда бот только добавляется в чат.

Отдельный сценарий - закрытые каналы со вступлением по заявке (join request). Там вместо прямой проверки подписки бот ловит апдейт chat_join_request и вызывает approveChatJoinRequest или declineChatJoinRequest - это уже не проверка существующей подписки, а модерация самого процесса вступления, и логика в боте строится иначе.

Когда проверка подписки - только часть воронки, я обычно выношу её результат за пределы бота: по факту успешной подписки бот бьёт вебхуком в n8n, а дальше сценарий в n8n обновляет карточку в CRM, ставит тег пользователю или запускает цепочку сообщений. Для хранения контактов подписчиков в такой связке важно держать базу на сервере в РФ, а не в иностранном сервисе - этого требует 152-ФЗ, если бот работает с российской аудиторией. Настройка такой связки с ботом и n8n - отдельная задача, для готового сценария под конкретный проект я делаю автоматизацию в n8n от 25 000 ₽.

Автоматизация в мессенджере

Telegram-бот / Mini App

от 30 000 ₽

Подробнее →

Частые вопросы

Нужно ли делать бота администратором канала для проверки подписки?

Да, для приватных каналов это обязательное условие - без прав администратора getChatMember вернёт ошибку доступа. Для публичных каналов бот тоже должен состоять в канале хотя бы как участник, иначе Telegram может отдавать нестабильный результат в зависимости от настроек приватности канала.

Можно ли проверить подписку без Bot API, просто по ссылке на канал?

Нет, надёжного способа снаружи Bot API не существует - количество подписчиков в публичном виде ничего не говорит о конкретном пользователе. Единственный рабочий путь - вызов getChatMember с id канала и id пользователя внутри самого бота.

Сколько стоит разработка бота с проверкой подписки на канал?

У меня разработка телеграм-бота начинается от 30 000 ₽ - цена зависит от того, нужна ли только проверка подписки или ещё интеграция с CRM, оплатой, несколькими каналами и автоматизацией. Точную оценку под конкретный сценарий даю на консультации, она стоит от 3 000 ₽.

Как часто нужно перепроверять подписку у уже пропущенных пользователей?

Зависит от ценности доступа. Для разового лид-магнита достаточно проверки один раз в момент выдачи. Для закрытого платного канала я обычно ставлю периодическую перепроверку раз в сутки через фоновую задачу или полагаюсь на апдейты chat_member, которые Telegram присылает сам при выходе пользователя из канала.

Есть задача?

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

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

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

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