Keyboards and callbacks
Attach an inline keyboard to a message. A callback button sends your bot a callback_query when pressed; a URL button opens a link after the app shows where it leads.
Sending a keyboard#
{"chat_id": -4503600130020245,
"text": "Lunch?",
"reply_markup": {"inline_keyboard": [
[{"text": "Pizza", "callback_data": "v:pizza"}, {"text": "Sushi", "callback_data": "v:sushi"}],
[{"text": "Menu", "url": "https://lunch.example.com/menu"}]]}}
reply_markup works on sendMessage, editMessageText, editMessageReplyMarkup and the three media methods. It may be an object or a JSON string.
Limits#
| Rows | at most 10, none empty |
| Buttons per row | at most 8 |
| Buttons in all | at most 64 |
| Button text | 1–40 characters, no control characters. Invisible formatting characters (direction marks, zero-width spaces, soft hyphens, …) are removed first, and a label that is blank after that is refused. Emoji sequences (ZWJ, variation selectors) are kept. |
callback_data | 1–64 bytes |
url | https://, a host, no user name or password, at most 512 characters |
| Fields | text and exactly one of callback_data or url; anything else (login_url, switch_inline_query, …) is refused |
A broken keyboard answers 422 invalid_markup with parameter: reply_markup. {"inline_keyboard": []} means no keyboard.
Before opening a URL button (or a text_link), the app shows its registrable domain, e.g. example.co.il. A domain is shown in Unicode only if each of its non-ASCII characters is an accented Latin letter (Latin-1 Supplement or Latin Extended-A, such as é or ü); anything else, including other scripts and look-alikes, is shown in its xn-- form, so pаypal.com with a Cyrillic “а” shows as xn--pypal-4ve.com.
Presses#
- A person presses a callback button. The app shows a spinner on it.
- Your bot receives a
callback_querywith the press'sid, who pressed (from), your message (with its keyboard) and the button'sdata. - You answer with
answerCallbackQuery: nothing (the spinner stops), a short toast (text, up to 200 characters), or an alert (show_alert: true). You may also edit the message.
{"callback_query_id": "01JB2G0M3K6V1S8T4W2Q9R5X7Y", "text": "Voted for pizza"}
- Answer within a few seconds. The app stops waiting after 15 seconds. The API accepts an answer for 15 minutes, once per press; later or repeated answers get
404 callback_not_found. callback_datanever reaches people's apps. Apps hold only a button id; Qivuna maps it back to your data. Only your bot sees its own data (inreply_markupof its messages and in presses).- A press counts as the person writing to the bot: in a 1:1 chat your answer within 24 hours is solicited.
- People can press at most 10 buttons in 10 seconds across all bots.
- A press reaches the bot only if the person can see the message: a member of the group or a subscriber of the channel where the bot is, or the person in a 1:1 chat that is not stopped.
Changing a keyboard#
editMessageReplyMarkup replaces or removes the keyboard of your own message, at any age. editMessageText also replaces it — and removes it if you leave reply_markup out, as in Telegram.
Every keyboard write gives the message a fresh random revision. A press on a copy of the keyboard that an app has not refreshed yet is refused in the app, so your bot never gets a press for a button that no longer exists.
Keyboards in encrypted chats#
In an encrypted bot's chats the keyboard is part of the signed, encrypted message (QB1 kb), never reply_markup: reply_markup next to an envelope is refused, and editMessageReplyMarkup in an encrypted bot's private chat answers 403 e2ee_required. Limits are stricter there: at most 8 rows, 1–4 buttons a row, 32 buttons, labels 1–40 characters. A press arrives as an encrypted_message with kind: "callback" and a callback_query_id; you answer with answerCallbackQuery {callback_query_id, envelope}. The callback data then comes from the person's phone, not from Qivuna's copy of your keyboard: treat it as untrusted input. See Encrypted bots.