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

Бот проверки подписки на канал: как устроен и что настроить

Бот проверки подписки на канал нужен, когда доступ к закрытому чату, файлу или скидке должен открываться только тем, кто реально состоит в канале, а не просто нажал кнопку «пропустить». Я такие боты собираю и на aiogram, и через связку нод в n8n, в зависимости от того, нужна гибкая логика или хватает одной проверки перед выдачей ссылки. Дальше разбираю саму механику, что нужно настроить в канале, как выглядит рабочий код и на чём чаще всего спотыкаются при первой настройке.

Как работает бот проверки подписки на канал

Механика простая на вид, вопросы обычно начинаются в деталях. Пользователь жмёт /start, бот показывает кнопку «Подписаться» со ссылкой на канал и кнопку «Я подписался». Когда пользователь нажимает вторую кнопку, бот обращается к методу getChatMember из Telegram Bot API, передавая id канала и id пользователя, и получает статус: creator, administrator, member, left или kicked. Статуса restricted у подписчика канала не будет: по документации Bot API ограниченный участник бывает только в супергруппах.

Если статус creator, administrator или member, считаю пользователя подписанным и открываю доступ. Если left (отписался) или kicked (забанен в канале), доступ закрыт. Никакого стороннего сервиса для этого не нужно, всю проверку делает один вызов API.

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

Способы проверки подписки: свой код или автоматизация без программирования

На практике есть два рабочих подхода.

Первый - написать бота самому на aiogram или другом фреймворке под Telegram Bot API. Это даёт полный контроль: можно проверять несколько каналов сразу, логировать попытки, привязать антифрод и завести данные в свою базу или CRM.

Второй - собрать проверку в n8n без отдельного бота с нуля. Через триггер на Telegram ловлю нажатие /start или колбэк с кнопки, через HTTP-запрос дёргаю тот же метод getChatMember у Telegram Bot API, а условной нодой сравниваю поле result.status со списком допустимых значений. Для одной воронки с одним каналом такой вариант настраивается быстрее, чем поднимать отдельный python-процесс. Когда сценариев становится больше и логика ветвится, разумнее сразу заказывать автоматизацию в n8n под конкретный процесс, а не собирать цепочку нод методом проб и ошибок.

По каким критериям я выбираю между вариантами:

  • Свой бот на aiogram: нужен сервер под python-процесс, зато логика любая, база данных, CRM, свои сценарии, подходит для воронок с несколькими шагами и хранением истории обращений.
  • Проверка через n8n: отдельный сервер под бота не нужен, но логика ограничена возможностями нод и HTTP-запросов, подходит для разовой проверки перед выдачей доступа.

Настраиваю бота проверки подписки: пошагово

Создаю бота и добавляю в канал администратором

Токен бота получаю у @BotFather командой /newbot. Дальше в самом канале захожу в раздел администраторов и добавляю туда бота. Права можно выдать по минимуму, отправка постов боту не нужна, важно только чтобы он числился администратором, иначе getChatMember не отработает.

Если канал приватный, кнопку «Подписаться» веду не на @username, а на инвайт-ссылку из настроек канала. Логика проверки при этом не меняется, бот всё так же смотрит статус участника по внутреннему chat_id вида ‑100…

Пишу проверку подписки на aiogram

Вот рабочий каркас на aiogram 3.x с проверкой подписки и обработкой обеих кнопок:

from aiogram import Bot, Dispatcher, F
from aiogram.filters import CommandStart
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.exceptions import TelegramBadRequest

bot = Bot(token="ТОКЕН_БОТА")
dp = Dispatcher()

CHANNEL_ID = "@my_channel"


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


async def is_subscribed(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 in ("creator", "administrator", "member")


@dp.message(CommandStart())
async def start(message: Message):
    if await is_subscribed(message.from_user.id):
        await message.answer("Доступ открыт.")
    else:
        await message.answer(
            "Подпишись на канал, чтобы продолжить:",
            reply_markup=subscribe_keyboard(),
        )


@dp.callback_query(F.data == "check_subscription")
async def check_subscription(callback: CallbackQuery):
    if await is_subscribed(callback.from_user.id):
        await callback.message.edit_text("Подписка подтверждена, доступ открыт.")
    else:
        await callback.answer(
            "Подписки пока не вижу, попробуй ещё раз.",
            show_alert=True,
        )

TelegramBadRequest ловлю на случай, если пользователь никогда не писал каналу или бот потерял доступ к чату. В таких ситуациях безопаснее считать пользователя неподписанным, чем ронять обработчик исключением.

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

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

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

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

Проверяю подписку сразу на несколько каналов

Часто нужно закрыть доступ не на один канал, а сразу на два-три, например основной канал плюс закрытый чат для покупателей. Логика та же, просто прогоняю пользователя по списку:

CHANNELS = ["@channel_one", "@channel_two", "-1001234567890"]


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

В список CHANNELS можно смешивать @username публичных каналов и числовые chat_id приватных, aiogram сам разберётся с форматом. Если проверяю сотни пользователей за короткое время, например во время розыгрыша, между запросами закладываю небольшие паузы через asyncio, чтобы не упереться в ограничения Telegram на частоту обращений к одному боту.

Частые ошибки при настройке проверки подписки

  • Бота не добавили администратором канала - getChatMember возвращает ошибку вместо статуса участника.
  • Кнопка «Подписаться» ведёт на один канал, а проверка идёт по chat_id другого - рассинхрон случается, когда меняли @username канала и забыли поправить код.
  • Статусы left и kicked обрабатывают одинаково, хотя это разные ситуации: просто отписался и забанен модератором канала.
  • Дёргают getChatMember без ограничения частоты при большом потоке пользователей и получают временные отказы от Telegram.
  • Хранят статус подписки в базе и не перепроверяют его в момент действия - пользователь давно отписался, а бот всё ещё считает его подписанным по старой записи.

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

Простой бот с проверкой одного канала и выдачей доступа после подтверждения подписки у меня стоит от 30 000 ₽. Если нужно несколько каналов, логирование обращений в базу, админ-панель или интеграция с CRM, объём работы больше, и оценка стартует выше базовой цены. Настройка проверки подписки через n8n без отдельного бота обходится от 25 000 ₽. Если сомневаетесь, какой вариант вообще нужен под вашу задачу, беру консультацию по архитектуре от 3 000 ₽, это дешевле, чем переделывать готового бота под другую логику.

Условия и возможности Telegram Bot API меняются, поэтому перед запуском я уточняю их у самого сервиса.

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

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

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

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

Да, getChatMember работает одинаково для публичных и приватных каналов, если бот туда добавлен админом. Разница только в том, что ссылку для подписки в приватный канал беру из инвайт-ссылки в настройках, а не из @username.

Как проверить подписку сразу на несколько каналов?

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

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

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

Есть задача?

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

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

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