Клавиатуры и кнопки в Telegram Bot API: полное руководство с примерами

Содержание
  1. Зачем нужны клавиатуры в Telegram-ботах
  2. Что изменилось в Telegram Bot API в 2025–2026
  3. Как сделать Inline Keyboard в Telegram Bot API
  4. Минимальный пример inline-клавиатуры
  5. Как работает нажатие inline-кнопки
  6. Пример Inline Keyboard на Python
  7. Reply Keyboard (ReplyKeyboardMarkup)
  8. Что это
  9. Основные параметры
  10. Полный пример: Reply Keyboard на Python
  11. Специальные типы кнопок Reply Keyboard
  12. Удаление Reply Keyboard
  13. Как показать клавиатуру одному пользователю в Telegram: канал, группа и selective=True
  14. Пример для python-telegram-bot v20+
  15. Пример для aiogram
  16. Что делать, если это именно канал
  17. Пример ограничения inline-кнопки по пользователю:
  18. Inline Keyboard (InlineKeyboardMarkup)
  19. Что это
  20. Полный пример: Inline Keyboard с callback_data
  21. Важные нюансы callback_data
  22. answer_callback_query: почему это обязательно
  23. Редактирование клавиатуры: edit_message_reply_markup
  24. Цветные стили кнопок: danger, success и primary
  25. Как это работает
  26. Премиум-эмодзи в тексте кнопок
  27. Пример: цветные кнопки на Python
  28. WebApp-кнопки — открытие Mini App
  29. 1. Reply Keyboard с Web App
  30. 2. Inline-кнопка с Web App
  31. 3. Menu Button — постоянная кнопка
  32. Кнопки оплаты Stars
  33. Как работает оплата через Stars
  34. Полный пример: оплата Stars
  35. sendMessageDraft: временный черновик ответа
  36. Как работает sendMessageDraft
  37. Пример: стриминг ответа от LLM
  38. Полная таблица всех типов кнопок (актуально для 2026)
  39. Когда какой тип использовать
  40. Лучшие практики
  41. 1. Не перегружайте клавиатуру
  42. 2. Всегда отвечайте на callback_query
  43. 3. Редактируйте, а не отправляйте заново
  44. 4. Цветные кнопки — для CTA, не для всех кнопок
  45. 5. Обрабатывайте неизвестные callback_data
  46. 6. Группируйте кнопки логически
  47. FAQ
  48. Чем отличается InlineKeyboard от ReplyKeyboard?
  49. Как сделать кнопку, которая открывает Mini App?
  50. Можно ли задать цвет кнопки?
  51. Как принять оплату через кнопку?
  52. Как сделать стриминг ответов как в ChatGPT?
  53. Сколько кнопок можно разместить в одном ряду?

Зачем нужны клавиатуры в Telegram-ботах

Telegram-бот без кнопок — это бот из 2015 года. Пользователи давно привыкли к интерактивным интерфейсам: нажал кнопку — получил результат. Никаких текстовых команд, никакого ручного ввода. Кнопки и клавиатуры — главный инструмент, который превращает бота из текстового терминала в полноценное приложение.

Что дают кнопки в практическом смысле:

  • Удобство — пользователь не запоминает команды, а выбирает из предложенных вариантов.
  • Скорость — одно нажатие вместо набора текста.
  • Снижение ошибок — бот получает предсказуемые данные, а не произвольный текст.
  • Конверсия — кнопка «Купить» или «Оставить заявку» работает лучше, чем текстовая инструкция.

В Telegram Bot API существует два принципиально разных класса клавиатур:

  1. Reply Keyboard (ReplyKeyboardMarkup) — кнопки, которые заменяют стандартную клавиатуру телефона. Отображаются внизу экрана.
  2. Inline Keyboard (InlineKeyboardMarkup) — кнопки, которые прикреплены непосредственно к сообщению. Отображаются под текстом сообщения

Что изменилось в Telegram Bot API в 2025–2026

Telegram Bot API регулярно обновляется, поэтому при работе с кнопками важно смотреть не только на старые примеры из библиотек, но и на актуальную официальную документацию. На момент обновления статьи актуальной является ветка Bot API 10.0 от 8 мая 2026 года.

  • Bot API 9.0 (лето 2025) — улучшенная поддержка Mini Apps, новые параметры для KeyboardButtonRequestUsers.
  • Bot API 9.3 (январь 2026) — появление sendMessageDraft для стриминга ответов бота, аналогично тому, как отвечает ChatGPT.
  • Bot API 9.4 (февраль 2026) — цветные кнопки: параметр button_color в InlineKeyboardButton, поддержка HEX-цветов и премиум-эмодзи в тексте кнопок.
  • Bot API 9.5 (март 2026) — sendMessageDraft доступен всем ботам без ограничений, расширены лимиты на количество кнопок.
  • Bot API 10.0 добавил guest mode, новые возможности для опросов, live photos и ряд изменений для chat management и business bots

Как сделать Inline Keyboard в Telegram Bot API

Inline Keyboard в Telegram Bot API

Если вам нужно быстро добавить кнопки под сообщением в Telegram-боте, используйте Inline Keyboard. Такая клавиатура прикрепляется к конкретному сообщению, а при нажатии кнопки бот получает не обычный текст, а специальное событие — callback_query

Inline Keyboard чаще всего используют для выбора действий, пагинации, подтверждения операций, открытия ссылок, запуска Mini App и обработки нажатий через callback_data

Минимальный пример inline-клавиатуры

Минимальный пример inline-клавиатуры
{
  "chat_id": 123456789,
  "text": "Выберите действие:",
  "reply_markup": {
    "inline_keyboard": [
      [
        {
          "text": "📋 Открыть каталог",
          "callback_data": "open_catalog"
        }
      ],
      [
        {
          "text": "🌐 Перейти на сайт",
          "url": "https://aglamov.biz"
        }
      ]
    ]
  }
}

Главное отличие: кнопка с callback_data отправляет боту callback_query, а кнопка с url просто открывает ссылку. Если вы ждёте обработку нажатия внутри бота, используйте callback_data

  • Кнопка с callback_data отправляет боту callback_query
  • Кнопка с url открывает ссылку и не отправляет callback боту
  • callback_data должен быть коротким — до 64 байт
  • После нажатия inline-кнопки бот должен ответить на callback, иначе у пользователя будет крутиться индикатор загрузки

Как работает нажатие inline-кнопки

Пользователь нажимает кнопку
↓
Telegram отправляет боту callback_query
↓
Бот вызывает answerCallbackQuery
↓
Бот редактирует сообщение или отправляет новый ответ

Пример Inline Keyboard на Python

Ниже пример для библиотеки python-telegram-bot. Бот показывает две inline-кнопки с callback_data и одну кнопку-ссылку

from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    keyboard = [
        [
            InlineKeyboardButton("📋 Каталог", callback_data="catalog"),
            InlineKeyboardButton("🛒 Корзина", callback_data="cart"),
        ],
        [
            InlineKeyboardButton("🌐 Сайт", url="https://example.com")
        ],
    ]

    await update.message.reply_text(
        "Выберите действие:",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )


async def handle_button(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query

    # Обязательно отвечаем на callback, чтобы убрать индикатор загрузки
    await query.answer()

    if query.data == "catalog":
        await query.edit_message_text("Вы открыли каталог товаров.")
    elif query.data == "cart":
        await query.edit_message_text("Ваша корзина пока пуста.")


def main() -> None:
    app = Application.builder().token("YOUR_BOT_TOKEN").build()

    app.add_handler(CommandHandler("start", start))
    app.add_handler(CallbackQueryHandler(handle_button))

    app.run_polling()


if __name__ == "__main__":
    main()

если бот не вызывает await query.answer(), пользователь может видеть бесконечный индикатор загрузки после нажатия inline-кнопки. Поэтому обработку callback_query почти всегда начинают с query.answer()

Если вам нужно постоянное меню внизу экрана, используйте ReplyKeyboardMarkup. Если нужны кнопки именно под сообщением, обработка нажатий, пагинация, подтверждение выбора, ссылки, Mini App или оплата, используйте InlineKeyboardMarkup

Ниже подробно разберём все варианты кнопок в Telegram Bot API: Reply Keyboard, Inline Keyboard, Web App-кнопки, оплату, обработку callback_query, частые ошибки и лучшие практики

Reply Keyboard (ReplyKeyboardMarkup)

Что это

Reply Keyboard — это набор кнопок, который отображается вместо стандартной клавиатуры телефона. Когда пользователь нажимает на такую кнопку, бот получает обычное текстовое сообщение с текстом кнопки. По сути, это быстрый ввод текста одним нажатием.

Reply Keyboard удобен для:

  • Главного меню бота
  • Навигации по разделам
  • Выбора из фиксированного списка вариантов

Основные параметры

ПараметрТипОписание
keyboardlist[list[KeyboardButton]]Двумерный массив кнопок (ряды и столбцы)
resize_keyboardboolПодгоняет размер клавиатуры под количество кнопок. По умолчанию False — клавиатура занимает столько же места, сколько стандартная
one_time_keyboardboolСкрывает клавиатуру после нажатия кнопки
input_field_placeholderstrТекст-подсказка в поле ввода (до 64 символов)
selectiveboolПоказывает клавиатуру только определённым пользователям в группе
is_persistentboolКлавиатура остаётся видимой даже после перезапуска бота

Полный пример: Reply Keyboard на Python

from telegram import Update, ReplyKeyboardMarkup, KeyboardButton
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Отправляет приветствие и показывает Reply Keyboard."""
    keyboard = [
        [KeyboardButton("📋 Каталог"), KeyboardButton("🛒 Корзина")],
        [KeyboardButton("📞 Поддержка"), KeyboardButton("ℹ️ О нас")],
        [KeyboardButton("📍 Отправить геолокацию", request_location=True)],
    ]

    reply_markup = ReplyKeyboardMarkup(
        keyboard,
        resize_keyboard=True,
        one_time_keyboard=False,
        input_field_placeholder="Выберите раздел..."
    )

    await update.message.reply_text(
        "Добро пожаловать! Выберите раздел:",
        reply_markup=reply_markup
    )


async def handle_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Обрабатывает нажатия кнопок Reply Keyboard."""
    text = update.message.text

    if text == "📋 Каталог":
        await update.message.reply_text("Вот наш каталог товаров...")
    elif text == "🛒 Корзина":
        await update.message.reply_text("Ваша корзина пуста.")
    elif text == "📞 Поддержка":
        await update.message.reply_text("Напишите ваш вопрос, мы ответим в течение часа.")
    elif text == "ℹ️ О нас":
        await update.message.reply_text("Мы — команда разработчиков ботов.")


async def handle_location(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Обрабатывает полученную геолокацию."""
    location = update.message.location
    await update.message.reply_text(
        f"Ваши координаты: {location.latitude}, {location.longitude}"
    )


def main() -> None:
    app = Application.builder().token("YOUR_BOT_TOKEN").build()

    app.add_handler(CommandHandler("start", start))
    app.add_handler(MessageHandler(filters.LOCATION, handle_location))
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_menu))

    app.run_polling()


if __name__ == "__main__":
    main()

Специальные типы кнопок Reply Keyboard

Помимо обычного текста, кнопки Reply Keyboard могут запрашивать у пользователя специальные данные:

from telegram import KeyboardButton, KeyboardButtonRequestChat, KeyboardButtonRequestUsers

# Запрос контакта (номер телефона)
btn_contact = KeyboardButton("📱 Отправить контакт", request_contact=True)

# Запрос геолокации
btn_location = KeyboardButton("📍 Отправить местоположение", request_location=True)

# Запрос создания опроса
btn_poll = KeyboardButton("📊 Создать опрос", request_poll=KeyboardButtonPollType(type="quiz"))

# Запрос выбора чата (Bot API 6.4+)
btn_chat = KeyboardButton(
    "Выбрать группу",
    request_chat=KeyboardButtonRequestChat(
        request_id=1,
        chat_is_channel=False,
        bot_is_member=True  # бот должен быть участником выбранного чата
    )
)

# Запрос выбора пользователей (Bot API 6.5+)
btn_users = KeyboardButton(
    "Выбрать пользователей",
    request_users=KeyboardButtonRequestUsers(
        request_id=2,
        user_is_bot=False,
        max_quantity=5  # можно выбрать до 5 пользователей
    )
)

KeyboardButtonRequestChat и KeyboardButtonRequestUsers особенно полезны, когда бот должен работать с конкретной группой или пользователем — например, для настройки рассылки или назначения администратора.

Удаление Reply Keyboard

Чтобы убрать Reply Keyboard, используйте ReplyKeyboardRemove:

from telegram import ReplyKeyboardRemove

await update.message.reply_text(
    "Клавиатура скрыта.",
    reply_markup=ReplyKeyboardRemove()
)

Как показать клавиатуру одному пользователю в Telegram: канал, группа и selective=True

В Telegram Bot API важно различать канал и группу / супергруппу. Если речь именно о Telegram-канале, показать ReplyKeyboardMarkup только одному подписчику нельзя: Telegram официально указывает, что ReplyKeyboardMarkup не поддерживается в каналах. Такая клавиатура предназначена для личных чатов, групп и супергрупп, но не для канальных постов. 

Если же под «каналом» имеется в виду общий чат / группа, тогда персональная клавиатура возможна через параметр selective=True. Но selective — это не список user_id. Это булевый флаг, а конкретный пользователь определяется Telegram автоматически: либо пользователь упомянут в тексте сообщения, либо бот отвечает реплаем на сообщение этого пользователя. Поэтому надёжный вариант — отправлять клавиатуру именно как replyна команду пользователя

Пример для python-telegram-bot v20+

from telegram import Update, ReplyKeyboardMarkup
from telegram.ext import Application, CommandHandler, ContextTypes


async def game(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    keyboard = [
        ["Ответ 1"],
        ["Ответ 2"],
        ["Отмена"],
    ]

    reply_markup = ReplyKeyboardMarkup(
        keyboard,
        resize_keyboard=True,
        one_time_keyboard=True,
        selective=True,
    )

    await update.message.reply_text(
        "Выберите вариант ответа:",
        reply_markup=reply_markup,
    )


def main() -> None:
    app = Application.builder().token("YOUR_BOT_TOKEN").build()
    app.add_handler(CommandHandler("game", game))
    app.run_polling()


if __name__ == "__main__":
    main()

Ключевой момент: использовать именно reply_text() / message.reply(), а не просто send_message(chat_id=group_id, ...). Если отправить обычное сообщение в группу без привязки к сообщению пользователя, Telegram не поймёт, кому именно показывать selective-клавиатуру

Пример для aiogram

from aiogram import Router, types
from aiogram.filters import Command

router = Router()


@router.message(Command("game"))
async def game(message: types.Message):
    keyboard = types.ReplyKeyboardMarkup(
        keyboard=[
            [types.KeyboardButton(text="Ответ 1")],
            [types.KeyboardButton(text="Ответ 2")],
            [types.KeyboardButton(text="Отмена")],
        ],
        resize_keyboard=True,
        one_time_keyboard=True,
        selective=True,
    )

    await message.reply(
        "Выберите вариант ответа:",
        reply_markup=keyboard,
    )

Здесь message.reply() важен по той же причине: бот отвечает на сообщение конкретного пользователя, и Telegram показывает клавиатуру только ему

Что делать, если это именно канал

Для настоящего Telegram-канала есть только обходные варианты:

  1. Отправить клавиатуру в личный чат пользователю через send_message(user_id, reply_markup=...). Но пользователь должен заранее открыть бота и нажать /start, иначе бот не сможет сам начать с ним диалог.
  2. Использовать Inline Keyboard в канальном посте, но понимать ограничение: inline-кнопки будут видны всем. Можно ограничить обработку на стороне бота — например, принимать нажатие только от нужного user_id, а остальным показывать уведомление «Эта кнопка не для вас». Inline Keyboard прикрепляется к сообщению и работает через CallbackQuery, а не через замену пользовательской клавиатуры

Пример ограничения inline-кнопки по пользователю:

ALLOWED_USER_ID = 123456789

async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query

    if query.from_user.id != ALLOWED_USER_ID:
        await query.answer("Эта кнопка не для вас.", show_alert=True)
        return

    await query.answer("Доступ разрешён.")
    await query.edit_message_text("Вы нажали персональную кнопку.")

Inline Keyboard (InlineKeyboardMarkup)

Что это

Inline Keyboard — кнопки, которые прикреплены непосредственно к конкретному сообщению бота. Они не заменяют клавиатуру, а отображаются под текстом сообщения. При нажатии на inline-кнопку пользователь не отправляет текстовое сообщение — вместо этого бот получает специальный объект CallbackQuery.

Inline-кнопки поддерживают несколько режимов работы:

  • callback_data — бот получает callback с произвольными данными (до 64 байт).
  • url — кнопка открывает ссылку в браузере.
  • switch_inline_query — переключает пользователя в inline-режим бота.
  • switch_inline_query_current_chat — аналогично, но в текущем чате.
  • login_url — авторизация через Telegram Login Widget.
  • web_app — открывает Mini App (подробнее в разделе ниже).
  • pay — кнопка оплаты (подробнее в разделе про Stars).

Полный пример: Inline Keyboard с callback_data

from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes


async def show_products(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Показывает список товаров с inline-кнопками."""
    keyboard = [
        [
            InlineKeyboardButton("🔵 Тариф Basic — $9/мес", callback_data="plan_basic"),
            InlineKeyboardButton("🟢 Тариф Pro — $29/мес", callback_data="plan_pro"),
        ],
        [
            InlineKeyboardButton("🟡 Тариф Enterprise — $99/мес", callback_data="plan_enterprise"),
        ],
        [
            InlineKeyboardButton("📖 Подробнее о тарифах", url="https://example.com/pricing"),
        ],
        [
            InlineKeyboardButton(
                "Попробовать бота в другом чате",
                switch_inline_query="тарифы"
            ),
        ],
    ]

    reply_markup = InlineKeyboardMarkup(keyboard)

    await update.message.reply_text(
        "Выберите тарифный план:",
        reply_markup=reply_markup
    )


async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Обрабатывает нажатие inline-кнопки."""
    query = update.callback_query

    # ОБЯЗАТЕЛЬНО: подтверждаем получение callback
    await query.answer()

    plan_names = {
        "plan_basic": "Basic ($9/мес)",
        "plan_pro": "Pro ($29/мес)",
        "plan_enterprise": "Enterprise ($99/мес)",
    }

    plan = plan_names.get(query.data, "Неизвестный")

    await query.edit_message_text(
        f"Вы выбрали тариф: {plan}\n\nДля подтверждения нажмите кнопку ниже.",
        reply_markup=InlineKeyboardMarkup([
            [InlineKeyboardButton("✅ Подтвердить", callback_data=f"confirm_{query.data}")],
            [InlineKeyboardButton("⬅️ Назад", callback_data="back_to_plans")],
        ])
    )


async def handle_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Обрабатывает подтверждение выбора."""
    query = update.callback_query

    if query.data == "back_to_plans":
        # Возвращаем исходную клавиатуру
        await query.answer()
        keyboard = [
            [
                InlineKeyboardButton("🔵 Basic — $9/мес", callback_data="plan_basic"),
                InlineKeyboardButton("🟢 Pro — $29/мес", callback_data="plan_pro"),
            ],
            [InlineKeyboardButton("🟡 Enterprise — $99/мес", callback_data="plan_enterprise")],
        ]
        await query.edit_message_text(
            "Выберите тарифный план:",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )
    elif query.data.startswith("confirm_"):
        await query.answer("Тариф активирован! 🎉", show_alert=True)
        await query.edit_message_text("Тариф успешно активирован. Спасибо!")


def main() -> None:
    app = Application.builder().token("YOUR_BOT_TOKEN").build()

    app.add_handler(CommandHandler("plans", show_products))
    app.add_handler(CallbackQueryHandler(handle_confirm, pattern="^(confirm_|back_)"))
    app.add_handler(CallbackQueryHandler(handle_callback, pattern="^plan_"))

    app.run_polling()


if __name__ == "__main__":
    main()

Важные нюансы callback_data

Параметр callback_data ограничен 64 байтами. Не пытайтесь передать в нём JSON-объект — используйте короткие идентификаторы:

# Плохо:
callback_data='{"action":"buy","product_id":12345,"quantity":2}'

# Хорошо:
callback_data="buy_12345_2"

Для хранения состояния используйте context.user_data или внешнее хранилище (Redis, базу данных).

answer_callback_query: почему это обязательно

После каждого нажатия inline-кнопки Telegram показывает индикатор загрузки. Если бот не вызовет answer_callback_query в течение 30 секунд, пользователь увидит ошибку. Поэтому всегда вызывайте await query.answer(), даже если вам не нужно показывать уведомление.

# Тихое подтверждение (без уведомления)
await query.answer()

# Всплывающее уведомление (toast)
await query.answer("Товар добавлен в корзину")

# Модальное окно с кнопкой OK
await query.answer("Оплата прошла успешно!", show_alert=True)

Редактирование клавиатуры: edit_message_reply_markup

Вместо отправки нового сообщения при каждом нажатии кнопки используйте редактирование существующего:

async def toggle_setting(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer()

    # Переключаем настройку
    current = context.user_data.get("notifications", True)
    context.user_data["notifications"] = not current

    new_status = "✅ Вкл" if not current else "❌ Выкл"

    new_keyboard = InlineKeyboardMarkup([
        [InlineKeyboardButton(
            f"Уведомления: {new_status}",
            callback_data="toggle_notifications"
        )],
    ])

    # Меняем только клавиатуру, не трогая текст сообщения
    await query.edit_message_reply_markup(reply_markup=new_keyboard)

Цветные стили кнопок: danger, success и primary

Начиная с Bot API 9.4, у кнопок KeyboardButton и InlineKeyboardButton появилось поле style. Оно позволяет визуально выделять кнопку одним из трёх стандартных стилей: danger, success или primary

Значение styleКак выглядитКогда использовать
dangerКрасный акцентУдаление, отмена, опасные действия
successЗелёный акцентПодтверждение, успешное действие, покупка
primaryСиний акцентОсновное действие, переход дальше, запуск сценария

Важно: style — это не произвольный HEX-цвет. Нельзя указать любой цвет кнопки через background_color или text_color. Telegram даёт только готовые стили, а точный внешний вид зависит от клиента Telegram

Как это работает

Параметр button_color принимает объект InlineKeyboardButtonColor с тремя полями:

ПолеТипОписание
background_colorintЦвет фона в формате RGB (24-bit integer)
text_colorintЦвет текста в формате RGB (24-bit integer)

Цвета задаются как целое число, соответствующее HEX-коду. Например, 0xFF0000 — красный, 0x00FF00 — зелёный, 0xFFFFFF — белый.

Премиум-эмодзи в тексте кнопок

Начиная с Bot API 9.4, в текст кнопок можно добавлять кастомные премиум-эмодзи через text_entities. Это позволяет использовать брендовые иконки прямо в кнопках.

Пример: цветные кнопки на Python

{
  "chat_id": 123456789,
  "text": "Подтвердить заказ №1284?",
  "reply_markup": {
    "inline_keyboard": [
      [
        {
          "text": "✅ Подтвердить",
          "callback_data": "confirm_order_1284",
          "style": "success"
        },
        {
          "text": "❌ Отмена",
          "callback_data": "cancel_order_1284",
          "style": "danger"
        }
      ],
      [
        {
          "text": "Подробнее",
          "callback_data": "order_details_1284",
          "style": "primary"
        }
      ]
    ]
  }
}

Цветные кнопки отображаются во всех актуальных клиентах Telegram (Android, iOS, Desktop). На старых версиях клиентов кнопка будет выглядеть стандартно — цвет просто игнорируется.

Если в вашей библиотеке для Python, Node.js или PHP поле style пока не поддерживается, можно отправить запрос напрямую в Bot API как JSON или обновить библиотеку до версии, которая уже поддерживает новые поля Telegram Bot API


WebApp-кнопки — открытие Mini App

Mini Apps (ранее Web Apps) — это полноценные веб-приложения, встроенные в интерфейс Telegram. Кнопки — основной способ открыть Mini App из бота. Существует три подхода, каждый для своего сценария.

Подробнее о создании Mini Apps читайте в нашем руководстве: Как создать своё приложение в Telegram и добавить в Telegram Apps Center.

1. Reply Keyboard с Web App

Кнопка отображается вместо клавиатуры. При нажатии открывается Mini App в модальном окне:

from telegram import Update, ReplyKeyboardMarkup, KeyboardButton, WebAppInfo
from telegram.ext import Application, CommandHandler, ContextTypes


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    keyboard = [
        [
            KeyboardButton(
                "📱 Открыть приложение",
                web_app=WebAppInfo(url="https://your-app.com")
            )
        ],
        [KeyboardButton("📞 Поддержка")],
    ]

    await update.message.reply_text(
        "Нажмите кнопку, чтобы открыть приложение:",
        reply_markup=ReplyKeyboardMarkup(keyboard, resize_keyboard=True)
    )


def main() -> None:
    app = Application.builder().token("YOUR_BOT_TOKEN").build()
    app.add_handler(CommandHandler("start", start))
    app.run_polling()


if __name__ == "__main__":
    main()

2. Inline-кнопка с Web App

Кнопка прикреплена к сообщению. Mini App открывается при нажатии:

from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, WebAppInfo
from telegram.ext import Application, CommandHandler, ContextTypes


async def shop(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    keyboard = [
        [
            InlineKeyboardButton(
                "🛒 Открыть магазин",
                web_app=WebAppInfo(url="https://shop.example.com")
            )
        ],
        [
            InlineKeyboardButton(
                "📋 Каталог товаров",
                web_app=WebAppInfo(url="https://shop.example.com/catalog")
            )
        ],
    ]

    await update.message.reply_text(
        "Добро пожаловать в наш магазин!",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )


def main() -> None:
    app = Application.builder().token("YOUR_BOT_TOKEN").build()
    app.add_handler(CommandHandler("shop", shop))
    app.run_polling()


if __name__ == "__main__":
    main()

3. Menu Button — постоянная кнопка

Menu Button отображается слева от поля ввода текста и доступен пользователю всегда, без отправки каких-либо команд. Идеально подходит для основного интерфейса бота:

from telegram import Update, MenuButtonWebApp, WebAppInfo
from telegram.ext import Application, CommandHandler, ContextTypes


async def setup_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Устанавливает постоянную кнопку меню с Mini App."""
    await context.bot.set_chat_menu_button(
        chat_id=update.effective_chat.id,
        menu_button=MenuButtonWebApp(
            text="Каталог",
            web_app=WebAppInfo(url="https://catalog.example.com")
        )
    )
    await update.message.reply_text(
        "Кнопка меню установлена! Нажмите на неё слева от поля ввода."
    )


async def remove_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Сбрасывает кнопку меню на стандартную."""
    from telegram import MenuButtonDefault
    await context.bot.set_chat_menu_button(
        chat_id=update.effective_chat.id,
        menu_button=MenuButtonDefault()
    )
    await update.message.reply_text("Кнопка меню сброшена.")


def main() -> None:
    app = Application.builder().token("YOUR_BOT_TOKEN").build()
    app.add_handler(CommandHandler("setup_menu", setup_menu))
    app.add_handler(CommandHandler("remove_menu", remove_menu))
    app.run_polling()


if __name__ == "__main__":
    main()

Пошаговую интеграцию Mini App в бота мы разобрали в отдельной статье: Интеграция Mini Apps в боты Telegram: пошаговое руководство.


Кнопки оплаты Stars

Telegram Stars — внутренняя валюта Telegram для оплаты цифровых товаров и услуг. Боты могут принимать оплату в Stars через встроенный платёжный интерфейс, без внешних платёжных систем.

Как работает оплата через Stars

  1. Бот отправляет invoice (счёт) с currency="XTR" (код валюты Stars).
  2. Пользователь видит сообщение с кнопкой оплаты.
  3. Telegram отправляет боту pre_checkout_query — бот подтверждает или отклоняет платёж.
  4. После успешной оплаты бот получает successful_payment.

Полный пример: оплата Stars

from telegram import Update, LabeledPrice, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
    Application,
    CommandHandler,
    PreCheckoutQueryHandler,
    MessageHandler,
    filters,
    ContextTypes,
)


async def sell_item(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Отправляет счёт на оплату в Stars."""
    await context.bot.send_invoice(
        chat_id=update.effective_chat.id,
        title="Премиум-подписка на 1 месяц",
        description="Доступ ко всем функциям бота: аналитика, отчёты, приоритетная поддержка.",
        payload="premium_1month",  # внутренний идентификатор товара
        currency="XTR",            # Stars
        prices=[LabeledPrice("Премиум-подписка", 150)],  # 150 Stars
        # Для Stars не нужен provider_token
    )


async def precheckout_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Обрабатывает pre-checkout запрос. Нужно ответить в течение 10 секунд."""
    query = update.pre_checkout_query

    # Проверяем, что payload корректный
    if query.invoice_payload == "premium_1month":
        await query.answer(ok=True)
    else:
        await query.answer(ok=False, error_message="Ошибка: неизвестный товар.")


async def successful_payment_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Вызывается после успешной оплаты."""
    payment = update.message.successful_payment

    await update.message.reply_text(
        f"Оплата прошла успешно!\n"
        f"Товар: {payment.invoice_payload}\n"
        f"Сумма: {payment.total_amount} Stars\n"
        f"ID транзакции: {payment.telegram_payment_charge_id}\n\n"
        f"Премиум-подписка активирована. Спасибо!"
    )

    # Здесь активируем подписку в базе данных
    # await activate_premium(update.effective_user.id, months=1)


async def refund_stars(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Возврат Stars пользователю (для администратора)."""
    # charge_id — ID транзакции из successful_payment
    charge_id = context.args[0] if context.args else None
    if not charge_id:
        await update.message.reply_text("Использование: /refund <charge_id>")
        return

    try:
        await context.bot.refund_star_payment(
            user_id=update.effective_user.id,
            telegram_payment_charge_id=charge_id
        )
        await update.message.reply_text("Возврат выполнен.")
    except Exception as e:
        await update.message.reply_text(f"Ошибка возврата: {e}")


def main() -> None:
    app = Application.builder().token("YOUR_BOT_TOKEN").build()

    app.add_handler(CommandHandler("buy", sell_item))
    app.add_handler(PreCheckoutQueryHandler(precheckout_handler))
    app.add_handler(MessageHandler(filters.SUCCESSFUL_PAYMENT, successful_payment_handler))
    app.add_handler(CommandHandler("refund", refund_stars))

    app.run_polling()


if __name__ == "__main__":
    main()

Кнопка оплаты всегда отображается первой в клавиатуре: InlineKeyboardButton(pay=True) автоматически помещается в первый ряд, независимо от порядка в коде.


sendMessageDraft: временный черновик ответа

sendMessageDraft — метод Telegram Bot API для показа пользователю промежуточного ответа, пока бот генерирует сообщение. Это полезно для AI-ботов, ассистентов, ботов поддержки и любых сценариев, где ответ может формироваться несколько секунд

Важно понимать ограничение: sendMessageDraft не создаёт постоянное сообщение в чате. Это временный черновик. Когда ответ готов, бот должен отправить финальный текст обычным методом sendMessage

Как работает sendMessageDraft

Бот начинает генерировать ответ
↓
Отправляет первый черновик через sendMessageDraft
↓
Обновляет черновик с тем же draft_id
↓
Когда ответ готов, отправляет финальное сообщение через sendMessage

Метод появился в Bot API 9.3 (январь 2026) в ограниченном тестовом режиме. С Bot API 9.5 (март 2026) sendMessageDraft доступен всем ботам без ограничений.

Пример: стриминг ответа от LLM

import requests
import time

TOKEN = "YOUR_BOT_TOKEN"
CHAT_ID = 123456789
DRAFT_ID = 1001

API_URL = f"https://api.telegram.org/bot{TOKEN}"


def send_message_draft(text: str) -> None:
    requests.post(
        f"{API_URL}/sendMessageDraft",
        json={
            "chat_id": CHAT_ID,
            "draft_id": DRAFT_ID,
            "text": text,
        },
        timeout=10,
    )


def send_final_message(text: str) -> None:
    requests.post(
        f"{API_URL}/sendMessage",
        json={
            "chat_id": CHAT_ID,
            "text": text,
        },
        timeout=10,
    )


parts = [
    "Готовлю ответ...",
    "Готовлю ответ...\n\nПроверяю данные...",
    "Готовлю ответ...\n\nПроверяю данные...\n\nФормирую итоговый текст...",
]

for part in parts:
    send_message_draft(part)
    time.sleep(1)

final_text = (
    "Готово. Inline Keyboard используется для кнопок под сообщением. "
    "Если кнопка содержит callback_data, бот получает callback_query. "
    "Если кнопка содержит url, пользователь просто открывает ссылку."
)

send_final_message(final_text)

Не путайте sendMessageDraft с обычным сообщением. Черновик нужен только для временного отображения процесса генерации. Финальный результат всё равно нужно отправить через sendMessage

Также у sendMessageDraft есть обязательный параметр draft_id. Он должен быть ненулевым. Если обновлять черновики с одним и тем же draft_id, изменения будут отображаться как продолжение одного и того же временного ответа

Стриминг особенно полезен для AI-ботов. Если вы разрабатываете бота с интеграцией нейросети, рекомендуем также ознакомиться с нашим руководством по созданию интерактивных приложений: Как создать тапалку для Telegram: пошаговое руководство.


Полная таблица всех типов кнопок (актуально для 2026)

ТипКлассГде отображаетсяДля чего
Reply KeyboardReplyKeyboardMarkupВместо клавиатурыМеню, навигация
Inline KeyboardInlineKeyboardMarkupПод сообщениемДействия, callback
Web App ButtonKeyboardButton(web_app=...)Вместо клавиатурыОткрытие Mini App
Inline Web AppInlineKeyboardButton(web_app=...)Под сообщениемОткрытие Mini App
Menu ButtonMenuButtonWebAppРядом с полем вводаПостоянный доступ к Mini App
Pay ButtonInlineKeyboardButton(pay=True)Под сообщениемОплата Stars
Login URLInlineKeyboardButton(login_url=...)Под сообщениемАвторизация через Telegram
Switch InlineInlineKeyboardButton(switch_inline_query=...)Под сообщениемПереход в inline-режим
Цветная кнопка (NEW)InlineKeyboardButton(button_color=...)Под сообщениемВизуальный акцент, CTA

Когда какой тип использовать

  • Reply Keyboard — для постоянного меню, к которому пользователь обращается регулярно. Пример: главное меню бота, выбор языка.
  • Inline Keyboard — для действий, привязанных к конкретному сообщению. Пример: лайк/дизлайк, выбор товара, пагинация.
  • Web App Button — когда нужен сложный интерфейс, который невозможно реализовать кнопками: формы, карты, дашборды.
  • Menu Button — когда Mini App является основным интерфейсом бота.
  • Pay Button — для продажи цифровых товаров за Stars.
  • Цветная кнопка — для выделения ключевого действия среди нескольких кнопок.

Лучшие практики

1. Не перегружайте клавиатуру

Максимум 3–4 кнопки в одном ряду. На мобильных устройствах узкие кнопки невозможно нажать точно. Если кнопок много — разбивайте на несколько рядов или используйте пагинацию:

# Плохо: 6 кнопок в одном ряду
keyboard = [[btn1, btn2, btn3, btn4, btn5, btn6]]

# Хорошо: по 3 кнопки в ряду
keyboard = [
    [btn1, btn2, btn3],
    [btn4, btn5, btn6],
]

2. Всегда отвечайте на callback_query

Каждое нажатие inline-кнопки должно получить ответ через answer_callback_query. Без этого пользователь видит бесконечный индикатор загрузки. Даже если обработка занимает время, сначала подтвердите получение:

async def handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer("Загрузка...")  # Сразу отвечаем
    # ... долгая операция

3. Редактируйте, а не отправляйте заново

Используйте edit_message_text и edit_message_reply_markup вместо отправки нового сообщения. Это сохраняет чат чистым и не создаёт спам из сообщений бота.

4. Цветные кнопки — для CTA, не для всех кнопок

Если все кнопки будут цветными, ни одна из них не будет выделяться. Используйте button_color только для одного ключевого действия: «Купить», «Подтвердить», «Начать». Остальные кнопки оставляйте стандартными.

5. Обрабатывайте неизвестные callback_data

Если пользователь нажмёт кнопку из старого сообщения, callback_data может не совпадать ни с одним из текущих обработчиков. Добавьте fallback-обработчик:

async def unknown_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer("Эта кнопка устарела. Используйте /start для начала.", show_alert=True)

# Добавляйте последним — он сработает, если не сработал ни один другой
app.add_handler(CallbackQueryHandler(unknown_callback))

6. Группируйте кнопки логически

Связанные действия размещайте в одном ряду, разные категории — в разных рядах. Деструктивные действия (удаление, отмена) визуально отделяйте от основных.


FAQ

Чем отличается InlineKeyboard от ReplyKeyboard?

ReplyKeyboard заменяет стандартную клавиатуру и отображается внизу экрана. При нажатии отправляется текстовое сообщение с текстом кнопки. InlineKeyboard прикреплена к конкретному сообщению и отображается под ним. При нажатии бот получает CallbackQuery — никакое сообщение в чат не отправляется.

Используйте Reply Keyboard для постоянного меню, Inline Keyboard — для действий в контексте конкретного сообщения.

Как сделать кнопку, которая открывает Mini App?

Три способа:

  1. Reply Keyboard: KeyboardButton(text="Открыть", web_app=WebAppInfo(url="https://..."))
  2. Inline-кнопка: InlineKeyboardButton(text="Открыть", web_app=WebAppInfo(url="https://..."))
  3. Menu Button: MenuButtonWebApp(text="Меню", web_app=WebAppInfo(url="https://..."))

URL приложения должен быть HTTPS. Подробности — в нашем руководстве по интеграции Mini Apps в боты Telegram.

Можно ли задать цвет кнопки?

Да, с Bot API 9.4 (февраль 2026). Используйте параметр button_color в InlineKeyboardButton:

InlineKeyboardButton(
    "Купить",
    callback_data="buy",
    button_color={"background_color": 0x43A047, "text_color": 0xFFFFFF}
)

Цветные кнопки работают только для Inline Keyboard. Reply Keyboard не поддерживает кастомные цвета.

Как принять оплату через кнопку?

Используйте метод send_invoice с currency="XTR" для оплаты в Telegram Stars. Для Stars не нужен provider_token — оплата обрабатывается внутри Telegram. Бот должен обрабатывать два события: pre_checkout_query (подтверждение перед оплатой) и successful_payment (после оплаты). Полный пример кода — в разделе «Кнопки оплаты Stars» выше.

Как сделать стриминг ответов как в ChatGPT?

Используйте метод sendMessageDraft (Bot API 9.3+, доступен всем с 9.5). Бот отправляет текст порциями, и пользователь видит постепенное появление ответа. После завершения генерации вызовите finalizeMessage, чтобы черновик стал обычным сообщением. Подробный пример — в разделе «sendMessageDraft» выше.

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

Технический лимит Telegram — 8 кнопок в одном ряду для Inline Keyboard и 12 кнопок для Reply Keyboard. Однако на практике рекомендуется использовать не более 3–4 кнопок в ряду, чтобы текст на них был читаемым на мобильных устройствах. Максимальное количество рядов — 100 для inline-клавиатуры.

Оцените статью
1 2 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x