Working with Keyboards
Choose a tool for developing with an AI agent:
- use Alaio Vibecode to build an app for Bitrix24 from a task description without knowing any programming language. The agent writes the code and deploys the app to a server, with no manual hosting setup
- use the MCP server to develop a REST API integration in your own project. The agent refers to the official REST documentation
KEYBOARD adds interactive buttons to a message: opening a link, inserting text into the input field, making a call, and other actions.
The keyboard is passed in the KEYBOARD parameter when sending or updating a message: im.message.add, im.message.update.
What You Can Do
- open a link:
LINK - insert or send text, copy it, make a call, open a chat:
ACTION - run a chatbot command:
COMMAND— works only in the keyboard of the bot itself - move the following buttons to a new line:
TYPE
Buttons are needed when the user is expected to act in response to the message. For other tasks, the section has its own mechanisms:
- format the message text — formatting
- attach structured blocks, images, or tables — attachments
- add items to the message context menu — menu
Button Fields
Buttons are listed in the KEYBOARD.BUTTONS array. Bitrix24 also accepts shorthand forms: an array of buttons without the BUTTONS wrapper, and the same structure as a JSON string — the wrapper is added automatically.
A regular button requires TEXT and at least one action: LINK, APP_ID, the ACTION and ACTION_VALUE pair, or COMMAND.
|
Name |
Description |
|
TEXT |
Button text |
|
LINK |
Link. Only addresses starting with |
|
ACTION |
Button action:
|
|
ACTION_VALUE |
Value for |
|
COMMAND |
Chatbot command. The leading The im.message.add and im.message.update methods send a message on behalf of the user, so a button with |
|
COMMAND_PARAMS |
Command parameters. Passed together with |
|
APP_ID |
ID of the chat application. Legacy scenario: the server accepts such a button, but the web messenger does not open the application from it. To open the application interface from a chat, use messenger widgets |
|
APP_PARAMS |
Application launch parameters. Passed together with |
|
TYPE |
Turns the array element into a service separator button. The only value is |
Besides the listed ones, ACTION accepts the service values LIVECHAT and HELP. The documentation does not describe their behavior in the interface — use the values from the table in your keyboards.
Ten more fields define the appearance and state of a button: BLOCK, DISABLED, CONTEXT, DISPLAY, WIDTH, BG_COLOR, BG_COLOR_TOKEN, TEXT_COLOR, OFF_BG_COLOR, OFF_TEXT_COLOR. They do not affect the button action; they are described in the Keyboards in Messages article.
The serialized keyboard must be shorter than 60,000 characters. A larger keyboard does not make it into the message, and im.message.update returns the KEYBOARD_OVERSIZE error.
Which Buttons Do Not Make It into the Message
A button does not make it into the message in two cases: it has no text, or Bitrix24 did not recognize any action. Unknown fields do not interfere: they have no effect on the button.
The button text cannot be empty, consist only of spaces, or be the string "0". Such a button does not make it into the message, whatever action you set.
The action is determined by the first suitable field in the order LINK, APP_ID, ACTION, COMMAND. An invalid field does not cause the button to be dropped — the check proceeds to the next field. For example, a button with an invalid link and a correct ACTION and ACTION_VALUE pair is sent: the action works, and the link is skipped.
Bitrix24 does not recognize the action if:
- the
ACTIONvalue is not among the allowed ones, orACTIONis passed withoutACTION_VALUE LINKfailed the format check- only
COMMANDis passed, and the message is sent by theim.message.*methods. Send buttons with commands using the methods of the Chatbots 2.0 section
There is no error in this case. If at least one working button remains in the keyboard, the method returns 200 and sends the message without the lost buttons. The KEYBOARD_ERROR error is returned only when no buttons remain
To verify that the entire keyboard was created, retrieve the sent message with the im.dialog.messages.get method: the keyboard is returned in result.messages[].params.KEYBOARD.
You cannot compare the response with the request verbatim: Bitrix24 adds service fields to every button, including TYPE, BOT_ID, BLOCK, DISABLED, DISPLAY, CONTEXT. Compare the set of buttons and their actions.
Line Break
The {"TYPE": "NEWLINE"} button is not displayed and performs no actions — it moves the following buttons to a new line. It has no other fields: TEXT and an action are not required.
When a message is retrieved, the separator is returned unchanged, and regular buttons get the service field TYPE with the value BUTTON.
Example of Sending a Message with a Keyboard
How to Use Examples in Documentation
curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"DIALOG_ID":"chat2725","MESSAGE":"Select an action","KEYBOARD":{"BUTTONS":[{"TEXT":"Open website","LINK":"https://www.example.com/"},{"TYPE":"NEWLINE"},{"TEXT":"Insert command","ACTION":"PUT","ACTION_VALUE":"/help"}]}}' \
https://**put_your_bitrix24_address**/rest/**put_your_user_id_here**/**put_your_webhook_here**/im.message.add
curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"DIALOG_ID":"chat2725","MESSAGE":"Select an action","KEYBOARD":{"BUTTONS":[{"TEXT":"Open website","LINK":"https://www.example.com/"},{"TYPE":"NEWLINE"},{"TEXT":"Insert command","ACTION":"PUT","ACTION_VALUE":"/help"}]},"auth":"**put_access_token_here**"}' \
https://**put_your_bitrix24_address**/rest/im.message.add
// This snippet is an ES module: top-level await requires type="module" or a bundler.
// $b24 is an already-initialized SDK instance (see the SDK "Get started" guide).
import { Text } from '@bitrix24/b24jssdk'
import type { B24Frame } from '@bitrix24/b24jssdk'
declare const $b24: B24Frame
try {
const response = await $b24.actions.v2.call.make<number>({
method: 'im.message.add',
params: {
DIALOG_ID: 'chat2725',
MESSAGE: 'Choose an action',
KEYBOARD: {
BUTTONS: [
{ TEXT: 'Open site', LINK: 'https://www.example.com/' },
{ TYPE: 'NEWLINE' },
{ TEXT: 'Insert command', ACTION: 'PUT', ACTION_VALUE: '/help' },
],
},
},
requestId: Text.getUuidRfc4122()
})
// The payload is available only on a successful response
if (!response.isSuccess) {
console.error(response.getErrorMessages().join('; '))
} else {
const result = response.getData()!.result
console.info('Created message ID:', result)
}
} catch (error) {
// Thrown on transport or SDK failures (AjaxError, SdkError, etc.)
console.error(error)
}
<!-- Load the SDK (UMD build); it is exposed as the global B24Js -->
<script src="https://unpkg.com/@bitrix24/b24jssdk@1/dist/umd/index.min.js"></script>
<script>
async function sendMessageWithKeyboard() {
try {
// Initialize the SDK inside a Bitrix24 frame
const $b24 = await B24Js.initializeB24Frame()
const response = await $b24.actions.v2.call.make({
method: 'im.message.add',
params: {
DIALOG_ID: 'chat2725',
MESSAGE: 'Choose an action',
KEYBOARD: {
BUTTONS: [
{ TEXT: 'Open site', LINK: 'https://www.example.com/' },
{ TYPE: 'NEWLINE' },
{ TEXT: 'Insert command', ACTION: 'PUT', ACTION_VALUE: '/help' },
],
},
},
requestId: B24Js.Text.getUuidRfc4122()
})
// The payload is available only on a successful response
if (!response.isSuccess) {
console.error(response.getErrorMessages().join('; '))
return
}
const result = response.getData().result
console.info('Created message ID:', result)
} catch (error) {
// Thrown on transport or SDK failures (AjaxError, SdkError, etc.)
console.error(error)
}
}
document.addEventListener('DOMContentLoaded', sendMessageWithKeyboard)
</script>
from b24pysdk.errors import BitrixAPIError, BitrixSDKException
try:
bitrix_response = client.im.message.add(
dialog_id="chat2725",
message="Select an action",
keyboard={
"BUTTONS": [
{
"TEXT": "Open the website",
"LINK": "https://www.example.com/",
},
{
"TYPE": "NEWLINE",
},
{
"TEXT": "Insert the command",
"ACTION": "PUT",
"ACTION_VALUE": "/help",
},
],
},
).response
result = bitrix_response.result
print(result)
except BitrixAPIError as error:
print(
"Bitrix API error",
f"error: {error.error}",
f"error_description: {error.error_description}",
sep="\n",
)
except BitrixSDKException as error:
print(f"Bitrix SDK error: {error.message}")
except Exception as error:
print(f"Unexpected error: {error}")
try {
$response = $b24Service
->core
->call(
'im.message.add',
[
'DIALOG_ID' => 'chat2725',
'MESSAGE' => 'Select an action',
'KEYBOARD' => [
'BUTTONS' => [
['TEXT' => 'Open website', 'LINK' => 'https://www.example.com/'],
['TYPE' => 'NEWLINE'],
['TEXT' => 'Insert command', 'ACTION' => 'PUT', 'ACTION_VALUE' => '/help'],
],
],
]
);
$result = $response
->getResponseData()
->getResult();
echo 'Created message ID: ' . $result;
} catch (Throwable $e) {
error_log($e->getMessage());
echo 'Error: ' . $e->getMessage();
}
BX24.callMethod(
'im.message.add',
{
DIALOG_ID: 'chat2725',
MESSAGE: 'Select an action',
KEYBOARD: {
BUTTONS: [
{ TEXT: 'Open website', LINK: 'https://www.example.com/' },
{ TYPE: 'NEWLINE' },
{ TEXT: 'Insert command', ACTION: 'PUT', ACTION_VALUE: '/help' },
],
},
},
function(result) {
if (result.error()) {
console.error(result.error().ex);
} else {
console.log(result.data());
}
}
);
require_once('crest.php');
$result = CRest::call(
'im.message.add',
[
'DIALOG_ID' => 'chat2725',
'MESSAGE' => 'Select an action',
'KEYBOARD' => [
'BUTTONS' => [
['TEXT' => 'Open website', 'LINK' => 'https://www.example.com/'],
['TYPE' => 'NEWLINE'],
['TEXT' => 'Insert command', 'ACTION' => 'PUT', 'ACTION_VALUE' => '/help'],
],
],
]
);
print_r($result);
// client and ctx are already created — see the Go SDK section
res, err := client.Core().Call(ctx, "im.message.add", b24.Params{
"DIALOG_ID": "chat2725",
"MESSAGE": "Select an action",
"KEYBOARD": b24.Params{
"BUTTONS": []b24.Params{
{
"TEXT": "Open website",
"LINK": "https://www.example.com/",
},
{
"TYPE": "NEWLINE",
},
{
"TEXT": "Insert command",
"ACTION": "PUT",
"ACTION_VALUE": "/help",
},
},
},
})
if err != nil {
return fmt.Errorf("im.message.add: %w", err)
}
// The response comes as json.RawMessage — parse it into a struct
// matching the response shape on the im.message.add method page.
fmt.Printf("%s\n", res.Result)
The current documentation on keyboards can be found in the Chatbots 2.0 section: