Как создать чат-бота для Открытых линий
Scope:
imbot,imopenlinesКто может выполнять методы: чтобы пройти сценарий целиком, нужно самое строгое из перечисленных прав — пользователь того приложения или вебхука, через который зарегистрирован чат-бот
- imbot.v2.Bot.register — авторизованный пользователь
- imopenlines.bot.session.message.send — любой пользователь
- imopenlines.bot.session.operator — любой пользователь
- imopenlines.bot.session.transfer и imopenlines.bot.session.finish — пользователь приложения с зарегистрированным чат-ботом
Выберите инструмент для разработки с AI-агентом:
- используйте Битрикс24 Вайбкод, чтобы создать приложение для Битрикс24 по описанию задачи без знания языков программирования. Агент напишет код и разместит приложение на сервере без ручной настройки хостинга
- используйте MCP-сервер, чтобы разрабатывать интеграцию через REST API в своем проекте. Агент будет обращаться к официальной REST-документации
Чат-бот для Открытых линий принимает обращения клиентов, отвечает первым сообщением и при необходимости передает диалог оператору. Для такого сценария используйте актуальную платформу чат-ботов 2.0.
Проверяемый результат: клиент пишет в канал Открытой линии, бот отвечает автоматическим сообщением, а по слову «оператор» передает диалог сотруднику. В чате линии видно и ответ бота, и подключившегося оператора.
В сценарии участвуют три объекта:
- входящий вебхук со scope
imbotиimopenlines - зарегистрированный чат-бот с поддержкой Открытых линий
- Открытая линия, к которой подключен этот бот
SDK выполняют только исходящие вызовы REST. Входящие события принимает ваш веб-сервер — например, приложение на Express, Flask или обычный PHP-скрипт.
Сценарий состоит из четырех шагов.
- Зарегистрировать бота с поддержкой Открытых линий методом imbot.v2.Bot.register и подключить его к линии
- Принять событие ONIMBOTV2MESSAGEADD в обработчике и проверить, что чат относится к Открытой линии
- Ответить клиенту методом imopenlines.bot.session.message.send
- Передать диалог оператору или завершить сессию методами imopenlines.bot.session.operator, imopenlines.bot.session.transfer и imopenlines.bot.session.finish
Порядок задан платформой: идентификатор чата для управления сессией появляется только в событии, а события приходят лишь после регистрации бота.
Подготовьте данные
Примеры на этой странице работают через входящий вебхук: он не требует установки приложения, и бот регистрируется в Битрикс24 сразу. Отличия сценария для приложения с OAuth-авторизацией собраны в блоке Что важно учитывать.
- Создайте входящий вебхук со scope
imbotиimopenlines - Разместите обработчик событий на публичном HTTPS-адресе, например
https://example.com/handler - Настройте Открытую линию и подключите к ней канал — Онлайн-чат на сайте или мессенджер
Создать вебхук и настроить Открытую линию может только администратор Битрикс24.
Подготовьте значения, которые нужно заменить своими:
|
Значение |
Откуда взять |
|
|
Адрес входящего вебхука вида |
|
|
Придумайте уникальный токен бота длиной до 40 символов. Он привязывается к боту при регистрации |
|
|
Публичный HTTPS-адрес обработчика событий. В примерах на JS и Python обработчик слушает путь |
|
|
Идентификатор сотрудника, которому бот передает диалог. Его видно в адресе профиля сотрудника или в ответе методов user.get и user.search — этим двум методам нужно отдельное право |
Идентификатор чата подставлять не нужно: он приходит в событии в поле data.chat.id и передается в параметр CHAT_ID методов управления сессией.
Адрес входящего вебхука и токен бота — секреты. Адрес дает весь доступ вебхука, токен позволяет управлять сессиями от имени бота. Храните оба значения в переменных окружения сервера и не размещайте их в коде, который выполняется в браузере.
Для Python-примера разложите адрес вебхука на домен B24_DOMAIN и путь B24_WEBHOOK_TOKEN вида 1/xxxxxxxxxxxxxxxx.
Инициализируйте SDK и прочитайте подготовленные значения перед первым вызовом.
Как использовать примеры в документации
// npm install express @bitrix24/b24jssdk
import { B24Hook, Text } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_WEBHOOK_URL)
const botToken = process.env.BOT_TOKEN
const handlerUrl = process.env.HANDLER_URL
const operatorId = Number(process.env.OPERATOR_ID)
// На ошибку REST SDK не бросает исключение, поэтому проверяем признак isSuccess
async function call(method, params) {
const response = await $b24.actions.v2.call.make({
method,
params,
requestId: Text.getUuidRfc4122(),
})
if (!response.isSuccess) {
throw new Error(response.getErrorMessages().join('; '))
}
return response.getData().result
}
# pip install b24pysdk flask
import os
from b24pysdk import BitrixWebhook, Client
token = BitrixWebhook(
domain=os.environ["B24_DOMAIN"],
webhook_token=os.environ["B24_WEBHOOK_TOKEN"],
)
client = Client(token)
bot_token = os.environ["BOT_TOKEN"]
handler_url = os.environ["HANDLER_URL"]
operator_id = int(os.environ["OPERATOR_ID"])
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Symfony\Component\EventDispatcher\EventDispatcher;
$log = new Logger('b24');
$log->pushHandler(new StreamHandler('php://stdout'));
$b24 = (new ServiceBuilderFactory(new EventDispatcher(), $log))
->initFromWebhook(getenv('B24_WEBHOOK_URL'));
$botToken = getenv('BOT_TOKEN');
$handlerUrl = getenv('HANDLER_URL');
$operatorId = (int)getenv('OPERATOR_ID');
Этот код нужен и разовому скрипту регистрации из шага 1, и постоянно работающему обработчику из шагов 2–4. Держите его в обоих файлах или вынесите в общий модуль.
1. Зарегистрируйте бота и подключите его к линии
В imbot.v2.Bot.register параметры бота передаются в объекте fields:
code— код бота, уникальный в рамках вебхука или приложенияbotToken— токен бота, обязателен при авторизации через входящий вебхукtype— тип ботаisSupportOpenline— поддержка Открытых линийeventMode— режим доставки событий, значениеwebhookотправляет события на адрес обработчика, отдельная подписка методомevent.bindне нужнаwebhookUrl— адрес обработчика событийproperties— профиль бота: имяname, должностьworkPositionи цвет аватараcolor
Тип выбирайте по задаче. Для гибридного бота, который работает в групповых чатах, личных диалогах и Открытых линиях, передайте type = bot и isSupportOpenline = true. Если бот нужен только для Открытых линий, передайте type = openline — поддержка линий включится сама. Без нее бот не получит события из чата линии.
Регистрация выполняется один раз. В примерах на Python и PHP SDK сами бросают исключение, если метод вернул ошибку, а в JS вызовы идут через функцию call из блока подготовки — она проверяет признак isSuccess.
// Для imbot.v2 нет типизированной обертки, поэтому используется прямой вызов через ядро SDK.
const result = await call('imbot.v2.Bot.register', {
fields: {
code: 'open_line_bot',
botToken: botToken,
type: 'bot',
isSupportOpenline: true,
eventMode: 'webhook',
webhookUrl: handlerUrl,
properties: {
name: 'Линия поддержки',
workPosition: 'Первая линия',
color: 'green',
},
},
})
const botId = Number(result.bot.id)
# Для imbot.v2 нет типизированной обертки, поэтому используется прямой вызов через ядро SDK.
response = token.call_method(
"imbot.v2.Bot.register",
{
"fields": {
"code": "open_line_bot",
"botToken": bot_token,
"type": "bot",
"isSupportOpenline": True,
"eventMode": "webhook",
"webhookUrl": handler_url,
"properties": {
"name": "Линия поддержки",
"workPosition": "Первая линия",
"color": "green",
},
}
},
)
bot_id = int(response["result"]["bot"]["id"])
// Для imbot.v2 нет типизированной обертки, поэтому используется прямой вызов через ядро SDK.
$result = $b24->core->call('imbot.v2.Bot.register', [
'fields' => [
'code' => 'open_line_bot',
'botToken' => $botToken,
'type' => 'bot',
'isSupportOpenline' => true,
'eventMode' => 'webhook',
'webhookUrl' => $handlerUrl,
'properties' => [
'name' => 'Линия поддержки',
'workPosition' => 'Первая линия',
'color' => 'green',
],
],
])->getResponseData()->getResult();
$botId = (int)$result['bot']['id'];
В успешном ответе сохраните result.bot.id: он нужен, когда вебхук управляет несколькими ботами. Поле isSupportOpenline подтверждает, что бот принят как бот Открытых линий. Пример сокращен, полная форма ответа — на странице imbot.v2.Bot.register.
{
"result": {
"bot": {
"id": 456,
"code": "open_line_bot",
"type": "bot",
"isSupportOpenline": true,
"eventMode": "webhook"
}
}
}
Метод идемпотентен: повторный вызов с тем же fields.code вернет существующего бота и не изменит его данные. Чтобы поменять свойства зарегистрированного бота или адрес обработчика, используйте imbot.v2.Bot.update.
После регистрации подключите бота к линии: откройте Контакт-центр > Открытые линии, отредактируйте нужную линию и укажите бота в блоке настроек чат-бота. Там же задается момент подключения — например, сразу при первом обращении клиента.
Пока бот не подключен к линии, он не входит в чат сессии. Метод регистрации при этом отработает успешно, но события ONIMBOTV2* из Открытой линии в обработчик не придут.
2. Примите событие и проверьте, что чат относится к линии
Битрикс24 отправляет события бота POST-запросом на адрес из fields.webhookUrl. Тело запроса приходит в формате application/x-www-form-urlencoded, ключи имеют вид data[chat][entityType] и auth[application_token]. Все скалярные значения передаются строками, поэтому приводите типы явно.
Обработчик принимает все события бота на одном адресе и разбирает их по полю event. Бот получает события из всех своих чатов, поэтому проверяйте поле data.chat.entityType: у чатов Открытых линий оно равно LINES.
Из события ONIMBOTV2MESSAGEADD возьмите два значения:
data.chat.id— идентификатор чата, его нужно передать в параметрCHAT_IDметодов управления сессиейdata.message.text— текст сообщения клиента, по нему бот выбирает ответ
Подлинность запроса проверяйте по auth.application_token с верхнего уровня, а не по токену из data.bot.auth. У бота, зарегистрированного через входящий вебхук, auth.application_token равен строке custom со склеенным botToken, без разделителя.
{
"event": "ONIMBOTV2MESSAGEADD",
"data": {
"bot": {"id": 456, "code": "open_line_bot"},
"message": {"id": 790, "chatId": 112, "authorId": 27, "text": "Нужен оператор"},
"chat": {"id": 112, "dialogId": "chat112", "type": "lines", "entityType": "LINES"},
"user": {"id": 27, "name": "Клиент"}
},
"auth": {"domain": "example.bitrix24.ru", "application_token": "custommy_bot_token"}
}
Пример показывает событие уже после разбора тела запроса. В самом запросе те же данные приходят плоскими ключами: data[chat][entityType]=LINES, data[chat][id]=112.
Функции sendReply и handleLinesMessage из шагов 3 и 4 разместите в этом же файле — обработчик вызывает их по имени.
// Инициализация SDK и переменные — из блока «Подготовьте данные»
import express from 'express'
const app = express()
app.use(express.urlencoded({ extended: true }))
app.post('/handler', async (req, res) => {
const data = req.body.data || {}
const auth = req.body.auth || {}
if (auth.application_token !== `custom${botToken}`) {
return res.sendStatus(403)
}
if (req.body.event === 'ONIMBOTV2MESSAGEADD' && data.chat?.entityType === 'LINES') {
const chatId = Number(data.chat.id)
const text = String(data.message?.text ?? '').trim().toLowerCase()
try {
await handleLinesMessage(chatId, text)
} catch (error) {
console.error(error)
}
}
// Платформа ждет ответ 200, повторная доставка события не гарантируется
res.sendStatus(200)
})
app.listen(3000)
# Инициализация SDK и переменные — из блока «Подготовьте данные»
import re
from flask import Flask, request
app = Flask(__name__)
def unflatten(form) -> dict:
"""Собирает плоские ключи вида data[chat][entityType] во вложенный словарь"""
result = {}
for key, value in form.items():
path = re.findall(r"[^\[\]]+", key)
node = result
for part in path[:-1]:
node = node.setdefault(part, {})
node[path[-1]] = value
return result
@app.post("/handler")
def handler():
payload = unflatten(request.form)
data = payload.get("data", {})
auth = payload.get("auth", {})
if auth.get("application_token") != f"custom{bot_token}":
return "", 403
chat = data.get("chat", {})
if payload.get("event") == "ONIMBOTV2MESSAGEADD" and chat.get("entityType") == "LINES":
chat_id = int(chat["id"])
text = (data.get("message", {}).get("text") or "").strip().lower()
try:
handle_lines_message(chat_id, text)
except Exception as error:
app.logger.error("%s", error)
# Платформа ждет ответ 200, повторная доставка события не гарантируется
return "", 200
if __name__ == "__main__":
app.run(port=3000)
// Продолжение handler.php: инициализация SDK и переменные — из блока «Подготовьте данные»
$event = (string)($_POST['event'] ?? '');
$data = (array)($_POST['data'] ?? []);
$auth = (array)($_POST['auth'] ?? []);
if (($auth['application_token'] ?? '') !== 'custom' . $botToken) {
http_response_code(403);
exit;
}
if ($event === 'ONIMBOTV2MESSAGEADD' && ($data['chat']['entityType'] ?? '') === 'LINES') {
$chatId = (int)($data['chat']['id'] ?? 0);
$text = mb_strtolower(trim((string)($data['message']['text'] ?? '')));
try {
handleLinesMessage($chatId, $text);
} catch (Throwable $exception) {
error_log($exception->getMessage());
}
}
// Платформа ждет ответ 200, повторная доставка события не гарантируется
http_response_code(200);
3. Ответьте клиенту
Сообщение от имени бота в текущую сессию линии отправляет метод imopenlines.bot.session.message.send. Параметры:
CHAT_ID— идентификатор чата, значениеdata.chat.idиз событияNAME— режим ответа:DEFAULTотправляет текст изMESSAGE,WELCOMEотправляет приветствие из настроек Открытой линии и игнорируетMESSAGEMESSAGE— текст ответа для режимаDEFAULT. С пустым текстом сообщение в чат не добавится
Метод работает с текущей сессией линии, CLIENT_ID ему не нужен. Оформите вызов отдельной функцией — она пригодится в шаге 4.
async function sendReply(chatId, message) {
await call('imopenlines.bot.session.message.send', {
CHAT_ID: chatId,
NAME: 'DEFAULT',
MESSAGE: message,
})
}
def send_reply(chat_id: int, message: str) -> None:
client.imopenlines.bot.session.message.send(
chat_id=chat_id,
message=message,
name="DEFAULT",
).response
function sendReply(int $chatId, string $message): void
{
global $b24;
$b24->core->call('imopenlines.bot.session.message.send', [
'CHAT_ID' => $chatId,
'NAME' => 'DEFAULT',
'MESSAGE' => $message,
]);
}
Ответ true подтверждает, что вызов выполнен. Метод не возвращает подтверждения, что сообщение появилось в чате, поэтому проверяйте результат в чате линии.
{
"result": true
}
4. Передайте диалог оператору или завершите сессию
Со scope imopenlines боту доступны три метода управления сессией:
- imopenlines.bot.session.operator — передать диалог первому свободному оператору линии, нужен только
CHAT_ID - imopenlines.bot.session.transfer — передать конкретному сотруднику в параметре
USER_IDили в очередь в параметреQUEUE_ID, за раз только одно назначение - imopenlines.bot.session.finish — завершить сессию
Методы imopenlines.bot.session.transfer и imopenlines.bot.session.finish действуют от имени бота, поэтому вебхук передает в параметр CLIENT_ID тот же botToken, который использовался при регистрации. У метода imopenlines.bot.session.operator параметра CLIENT_ID нет.
Флаг LEAVE в методе imopenlines.bot.session.transfer определяет, останется ли бот в чате: Y — бот выходит сразу, N — остается до подтверждения передачи. Значение по умолчанию — N.
Соберите ветки ответа в функцию handleLinesMessage, которую вызывает обработчик из шага 2. Пример разбирает три ключевых слова:
- «оператор» — передать диалог первому свободному сотруднику линии
- «менеджер» — передать диалог сотруднику из
OPERATOR_ID - «спасибо» — попрощаться и завершить сессию
На остальные сообщения бот отвечает подсказкой.
async function handleLinesMessage(chatId, text) {
if (text.includes('оператор')) {
await call('imopenlines.bot.session.operator', { CHAT_ID: chatId })
return
}
if (text.includes('менеджер')) {
await call('imopenlines.bot.session.transfer', {
CHAT_ID: chatId,
USER_ID: operatorId,
LEAVE: 'Y',
CLIENT_ID: botToken,
})
return
}
if (text === 'спасибо') {
await sendReply(chatId, 'Рады помочь! Обращайтесь еще')
await call('imopenlines.bot.session.finish', {
CHAT_ID: chatId,
CLIENT_ID: botToken,
})
return
}
await sendReply(chatId, 'Здравствуйте! Опишите вопрос или напишите «оператор», чтобы подключить сотрудника')
}
def handle_lines_message(chat_id: int, text: str) -> None:
if "оператор" in text:
client.imopenlines.bot.session.operator(chat_id=chat_id).response
return
if "менеджер" in text:
# Типизированная обертка не принимает CLIENT_ID, поэтому вызываем метод через ядро SDK
token.call_method(
"imopenlines.bot.session.transfer",
{
"CHAT_ID": chat_id,
"USER_ID": operator_id,
"LEAVE": "Y",
"CLIENT_ID": bot_token,
},
)
return
if text == "спасибо":
send_reply(chat_id, "Рады помочь! Обращайтесь еще")
token.call_method(
"imopenlines.bot.session.finish",
{"CHAT_ID": chat_id, "CLIENT_ID": bot_token},
)
return
send_reply(chat_id, "Здравствуйте! Опишите вопрос или напишите «оператор», чтобы подключить сотрудника")
function handleLinesMessage(int $chatId, string $text): void
{
global $b24, $botToken, $operatorId;
if (str_contains($text, 'оператор')) {
$b24->core->call('imopenlines.bot.session.operator', ['CHAT_ID' => $chatId]);
return;
}
if (str_contains($text, 'менеджер')) {
$b24->core->call('imopenlines.bot.session.transfer', [
'CHAT_ID' => $chatId,
'USER_ID' => $operatorId,
'LEAVE' => 'Y',
'CLIENT_ID' => $botToken,
]);
return;
}
if ($text === 'спасибо') {
sendReply($chatId, 'Рады помочь! Обращайтесь еще');
$b24->core->call('imopenlines.bot.session.finish', [
'CHAT_ID' => $chatId,
'CLIENT_ID' => $botToken,
]);
return;
}
sendReply($chatId, 'Здравствуйте! Опишите вопрос или напишите «оператор», чтобы подключить сотрудника');
}
Успешный ответ каждого метода управления сессией:
{
"result": true
}
Проверим результат
- Проверьте регистрацию методом imbot.v2.Bot.list с параметром
botToken— в массивеresult.botsесть бот с вашимcode, у негоisSupportOpenlineравенtrue, аeventModeравенwebhook - Напишите в канал, подключенный к линии. В обработчик придет
ONIMBOTV2MESSAGEADD, в которомdata.chat.entityTypeравенLINES, а бот ответит текстом из шага 3 - Напишите «оператор». Метод
imopenlines.bot.session.operatorвернетtrue, и к диалогу подключится сотрудник линии
Диалог целиком виден в разделе Контакт-центр > Открытые линии: в истории сессии есть и ответы бота, и момент передачи оператору.
Ошибки и диагностика
Если метод вернул ошибку, проверьте данные запроса и scope вебхука.
BOT_TOKEN_NOT_SPECIFIED— не переданfields.botToken, при авторизации через вебхук он обязателенBOT_TOKEN_INVALID_LENGTH— токен бота длиннее 40 символов, укоротите значениеBOT_TOKENBOT_WEBHOOK_URL_REQUIRED— для webhook-режима не переданfields.webhookUrl, подставьте адрес обработчикаBOT_INVALID_CALLBACK— вfields.webhookUrlпередан невалидный адрес, проверьте схемуhttpsи доменное имяBOT_CODE_ALREADY_TAKEN— код бота занят, выберите другое значениеfields.codeCHAT_ID_EMPTY— не переданCHAT_IDили передано значение<= 0, возьмитеdata.chat.idиз событияUSER_ID_EMPTY— вimopenlines.bot.session.transferпередан пустойUSER_IDили значение<= 0. Так бывает, когда переменнаяOPERATOR_IDне задана в окруженииBOT_ID_ERROR— вCLIENT_IDпередано значение, для которого нет зарегистрированного бота, сравните его сfields.botTokenиз шага 1ACCESS_DENIED— параметрCLIENT_IDне передан вовсе или у вебхука нет scopeimopenlines, проверьте оба условияWRONG_CHAT— диалог уже ведет оператор, а не бот, передавать сессию повторно не нужноOPERATOR_WRONG— передать диалог указанному сотруднику или в очередь нельзя, проверьтеUSER_ID
Если ошибки нет, но бот молчит, пройдите цепочку по шагам:
- события не приходят в обработчик — бот не подключен к линии в настройках Открытой линии или в
fields.eventModeосталось значениеfetch. Проверьте бота методомimbot.v2.Bot.listи повторите шаг 1 - события приходят, но обработчик отвечает
403— значениеBOT_TOKENв окружении сервера не совпадает с токеном регистрации, сравните его сfields.botTokenиз шага 1 - события приходят, но условие не срабатывает — сравните
data.chat.entityTypeсо строкойLINESи помните, что в webhook-режиме все скаляры приходят строками - обработчик отвечает не
200— платформа не гарантирует повторную доставку события, диалог останется без ответа
Что важно учитывать
- Методы и события ветки
imbot.*устарели. Для новых ботов используйтеimbot.v2.*, порядок перехода описан в статье Миграция с imbot на imbot.v2 - Бот, зарегистрированный методами
imbot.*, получает событияONIMBOT*, а бот изimbot.v2.*— событияONIMBOTV2* - В чате Открытой линии бот получает все сообщения клиента без упоминания
@bot, в отличие от групповых чатов - В приложении с OAuth-авторизацией
fields.botTokenпри регистрации иCLIENT_IDпри управлении сессией не нужны: бот привязан к приложению черезclient_id. При этом события не приходят, пока приложение не завершило установку - Чтобы адаптировать сценарий под свою задачу, меняйте только функцию
handleLinesMessage: условия на текст, тексты ответов и способ передачи диалога - Чтобы направлять обращения в очередь, передавайте в
imopenlines.bot.session.transferпараметрQUEUE_IDсо значением поляIDиз ответа метода imopenlines.config.list.get