Issue with Button Data in Python Telegram Bot

I am working on a simple Telegram bot using python-telegram-bot library. The bot includes a button which, upon being clicked, should make an HTTP request and display the response data without opening a browser. Here’s my current code:

import requests
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import CallbackContext
from telegram.constants import ParseMode


def fetch_data():
    response = requests.get('https://jsonplaceholder.typicode.com/comments/1')
    return response.json()['body']


def start(update: Update, context: CallbackContext) -> None:
    buttons = [
        [InlineKeyboardButton('Get data', callback_data=fetch_data())]
    ]
    reply_markup = InlineKeyboardMarkup(buttons)
    update.message.reply_text('Choose an option:', reply_markup=reply_markup)


def button(update: Update, context: CallbackContext) -> None:
    query = update.callback_query
    query.answer()
    content = f"<h3>{query.data}</h3>"
    query.edit_message_text(text=content, parse_mode=ParseMode.HTML)

This code works fine for static data and text shorter than 50 characters. But when the text exceeds 80 characters, I get this error:

telegram.error.BadRequest: Button_data_invalid

Is this a Telegram API limitation (64-byte text limit)? How do some bots display messages with thousands of characters? What am I missing?

I’ve dealt with this issue before, and it’s definitely related to the 64-byte limit for callback_data. Here’s what worked for me:

Instead of passing the entire data through the button, I used a unique identifier and stored the actual data server-side. My approach was to use Redis as a temporary cache, but you could also use a database or even a simple dictionary in memory (though this isn’t ideal for production).

Here’s a quick example of how I modified the code:

import uuid
from telegram import InlineKeyboardButton, InlineKeyboardMarkup

# Assuming you have a cache or database set up
cache = {}

def start(update, context):
    data_id = str(uuid.uuid4())
    cache[data_id] = fetch_data()
    button = InlineKeyboardButton('Get data', callback_data=f'get:{data_id}')
    update.message.reply_text('Choose:', reply_markup=InlineKeyboardMarkup([[button]]))

def button(update, context):
    query = update.callback_query
    action, data_id = query.data.split(':')
    if action == 'get':
        data = cache.get(data_id, 'Data not found')
        query.edit_message_text(text=data)
    query.answer()

This way, you’re only passing a short identifier in the callback_data, staying well within the 64-byte limit. When the button is clicked, you fetch the full data using this identifier. It’s worked great for me, even with very large responses.

hey mate, i had a similar issue. store large text in bot_data and pass a short id in callback_data. for example:

def start(u,c):
    c.bot_data['d']=fetch_data()
    u.message.reply_text('choose', reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton('data', callback_data='id')]]))
def button(u,c):
    u.callback_query.edit_message_text(c.bot_data['d'])

this way you avoid the 64-byte limit. hope it helps!