Как интегрировать внешнюю телефонию с Битрикс24
- Что нужно до начала
- Инициализация SDK
- 1. Соберем приложение
- 2. Зарегистрируем входящий звонок
- 3. Покажем звонок группе сотрудников
- 4. Направим звонок ответственному за клиента
- 5. Обработаем исходящий звонок из CRM
- 6. Завершим звонок и сохраним результат
- Зафиксируем звонок без показа карточки
- Проверим результат
- Ошибки и диагностика
- Что важно учитывать
- Продолжите изучение
Scope:
telephonyКто может выполнять методы: чтобы пройти сценарий целиком, нужно установленное приложение с OAuth-авторизацией
- telephony.externalLine.add, telephony.externalCall.register, telephony.externalCall.finish и event.bind — пользователь, под которым приложение получило OAuth-авторизацию
- telephony.externalCall.searchCrmEntities, telephony.externalCall.show, telephony.externalCall.hide, telephony.externalCall.attachRecord и telephony.call.attachTranscription — любой пользователь
Выберите инструмент для разработки с AI-агентом:
- используйте Битрикс24 Вайбкод, чтобы создать приложение для Битрикс24 по описанию задачи без знания языков программирования. Агент напишет код и разместит приложение на сервере без ручной настройки хостинга
- используйте MCP-сервер, чтобы разрабатывать интеграцию через REST API в своем проекте. Агент будет обращаться к официальной REST-документации
Внешняя телефония передает в Битрикс24 данные о звонках из АТС: номер клиента, пользователя, линию, статус разговора и запись. Битрикс24 показывает карточку звонка сотруднику, связывает звонок с CRM и сохраняет результат в статистике.
Сценарий состоит из шести шагов.
- Соберем приложение и обработчики для АТС и Битрикс24
- Зарегистрируем входящий звонок
- Покажем карточку звонка группе сотрудников
- Направим звонок ответственному за клиента
- Обработаем исходящий звонок из CRM
- Завершим звонок и сохраним результат
Отдельно разберем сценарий, когда звонок нужно зафиксировать без показа карточки.
Методы telephony.externalLine.add, telephony.externalCall.register и telephony.externalCall.finish работают только в контексте приложения. Входящий вебхук для них не подходит. Событие ONEXTERNALCALLSTART (шаг 5) принимает ваш веб-сервер.
В PHP методы телефонии вызываются напрямую через ядро ($b24->core->call(...)). Типизированные аналоги есть в getTelephonyScope()->externalCall() (show, hide, register, finishForUserId) и ->externalLine(), но они требуют объектов-значений (CallType, TelephonyCallStatusCode, Money, CarbonImmutable).
Что нужно до начала
Перед началом убедитесь, что у вас есть:
- локальное или тиражное приложение со scope
telephonyи сохраненной OAuth-авторизацией - публичный HTTPS-обработчик приложения, если нужно принимать событие
ONEXTERNALCALLSTART. Входящий вебхук не получает это событие - идентификатор сотрудника
USER_ID, которому будет показана карточка звонка - номер внешней линии
LINE_NUMBER, напримерline-1 - уникальный идентификатор звонка на стороне АТС
EXTERNAL_CALL_ID - публичный URL записи разговора, если запись передается методом
telephony.externalCall.attachRecord
Значения your-domain.bitrix24.ru, 1269, 1270, 1271, line-1, asterisk-1773130778.18441 и URL записи замените на данные своего Битрикс24, сотрудников, линии и АТС.
Как использовать примеры в документации
Инициализация SDK
В примерах ниже $b24 для JS, $b24 для PHP и client для Python — уже инициализированные клиенты с OAuth-токеном установленного приложения. Получение, хранение и продление OAuth-токенов описаны в статье Полный протокол авторизации OAuth 2.0.
1. Соберем приложение
Рабочая интеграция обычно состоит из серверного приложения и обработчиков для АТС и Битрикс24:
- Создайте локальное приложение или приложение для Маркета
- Завершите установку приложения и сохраните авторизацию
- Зарегистрируйте внешнюю линию методом telephony.externalLine.add. Номер линии передавайте в
LINE_NUMBERметода telephony.externalCall.register - Подпишите приложение на ONEXTERNALCALLSTART методом event.bind, если нужно запускать исходящие звонки из CRM
- Создайте обработчик событий от АТС, который вызывает
telephony.externalCall.register/show/hide/finishпо состоянию звонка - Создайте обработчик ONEXTERNALCALLSTART для исходящих звонков
- Если запись разговора появляется после завершения, прикрепите ее методом telephony.externalCall.attachRecord
Регистрация внешней линии создает номер, который связывает звонки с приложением. Этот номер передавайте в LINE_NUMBER при регистрации звонка.
const response = await $b24.actions.v2.call.make({
method: 'telephony.externalLine.add',
params: { NUMBER: 'line-1', NAME: 'Внешняя линия' },
requestId: 'line-add',
})
client.telephony.external_line.add(number="line-1", name="Внешняя линия").response
$b24->core->call('telephony.externalLine.add', [
'NUMBER' => 'line-1',
'NAME' => 'Внешняя линия',
]);
Успешный ответ содержит идентификатор созданной линии.
{
"result": {
"ID": 7
}
}
Если нужно обрабатывать исходящие звонки из CRM, подпишите приложение на событие ONEXTERNALCALLSTART. В handler передайте публичный HTTPS URL вашего обработчика.
await $b24.actions.v2.call.make({
method: 'event.bind',
params: {
event: 'ONEXTERNALCALLSTART',
handler: 'https://your-domain.example/events',
},
requestId: 'event-bind',
})
client.event.bind(
event="ONEXTERNALCALLSTART",
handler="https://your-domain.example/events",
).response
$b24->core->call('event.bind', [
'event' => 'ONEXTERNALCALLSTART',
'handler' => 'https://your-domain.example/events',
]);
Успешная подписка возвращает true.
{
"result": true
}
2. Зарегистрируем входящий звонок
Когда АТС получает входящий звонок, вызовите telephony.externalCall.register:
USER_ID— сотрудник, которому показать карточкуPHONE_NUMBER— номер клиентаTYPE = 2— входящий звонокLINE_NUMBER— номер внешней линииEXTERNAL_CALL_ID— уникальный идентификатор звонка на стороне АТСSHOW = 1(или не передавать) — карточка откроется у пользователя изUSER_ID
Метод вернет CALL_ID для дальнейших действий (show, hide, finish, attachRecord).
const response = await $b24.actions.v2.call.make({
method: 'telephony.externalCall.register',
params: {
USER_ID: 1269,
PHONE_NUMBER: '79062195047',
TYPE: 2,
LINE_NUMBER: 'line-1',
EXTERNAL_CALL_ID: 'asterisk-1773130778.18441',
SHOW: 1,
},
requestId: 'call-register',
})
const callId = response.getData().result.CALL_ID
bitrix_response = client.telephony.external_call.register(
phone_number="79062195047",
call_type=2,
user_id=1269,
line_number="line-1",
external_call_id="asterisk-1773130778.18441",
show=1,
).response
call_id = bitrix_response.result["CALL_ID"]
$response = $b24->core->call('telephony.externalCall.register', [
'USER_ID' => 1269,
'PHONE_NUMBER' => '79062195047',
'TYPE' => 2,
'LINE_NUMBER' => 'line-1',
'EXTERNAL_CALL_ID' => 'asterisk-1773130778.18441',
'SHOW' => 1,
]);
$callId = $response->getResponseData()->getResult()['CALL_ID'];
Успешный ответ содержит CALL_ID. Сохраните его: этот идентификатор нужен для показа, скрытия, завершения звонка и прикрепления записи.
{
"result": {
"CALL_ID": "externalCall.716f1cb73def9700a23842adf9c4c568.1773130779",
"CRM_CREATED_LEAD": null,
"CRM_CREATED_ENTITIES": [],
"CRM_ENTITY_TYPE": "CONTACT",
"CRM_ENTITY_ID": 797
}
}
3. Покажем звонок группе сотрудников
Одновременная очередь. Передайте массив идентификаторов сотрудников в USER_ID метода telephony.externalCall.show. Когда оператор ответит, скройте карточку у остальных методом telephony.externalCall.hide.
В примере карточка показывается трем сотрудникам, затем, когда отвечает сотрудник 1270, скрывается у остальных.
const queue = [1269, 1270, 1271]
await $b24.actions.v2.call.make({
method: 'telephony.externalCall.show',
params: { CALL_ID: callId, USER_ID: queue },
requestId: 'call-show',
})
const answeredUserId = 1270
const usersToHide = queue.filter((userId) => userId !== answeredUserId)
await $b24.actions.v2.call.make({
method: 'telephony.externalCall.hide',
params: { CALL_ID: callId, USER_ID: usersToHide },
requestId: 'call-hide',
})
queue = [1269, 1270, 1271]
client.telephony.external_call.show(call_id=call_id, user_id=queue).response
answered_user_id = 1270
users_to_hide = [uid for uid in queue if uid != answered_user_id]
client.telephony.external_call.hide(call_id=call_id, user_id=users_to_hide).response
$queue = [1269, 1270, 1271];
// Типизированный аналог: $b24->getTelephonyScope()->externalCall()->show($callId, $queue);
$b24->core->call('telephony.externalCall.show', [
'CALL_ID' => $callId,
'USER_ID' => $queue,
]);
$answeredUserId = 1270;
$usersToHide = array_values(array_filter($queue, fn($userId) => $userId !== $answeredUserId));
$b24->core->call('telephony.externalCall.hide', [
'CALL_ID' => $callId,
'USER_ID' => $usersToHide,
]);
Методы show и hide возвращают true, если команда показа или скрытия карточки отправлена.
{
"result": true
}
Последовательная очередь. Покажите карточку первому сотруднику методом show. Если он не ответил за заданное в АТС время, скройте карточку методом hide и покажите следующему сотруднику методом show.
4. Направим звонок ответственному за клиента
Чтобы показать звонок ответственному менеджеру, сначала найдите клиента по телефону методом telephony.externalCall.searchCrmEntities. Метод вернет найденные объекты CRM и ASSIGNED_BY_ID — идентификатор ответственного сотрудника.
Затем зарегистрируйте звонок методом telephony.externalCall.register с параметрами:
USER_ID—ASSIGNED_BY_IDиз результата поискаPHONE_NUMBER— номер клиентаTYPE = 2— входящий звонокLINE_NUMBER— номер внешней линииEXTERNAL_CALL_ID— уникальный идентификатор звонка на стороне АТСSHOW = 0— не показывать карточку сразу после регистрации
После регистрации передайте CALL_ID и ASSIGNED_BY_ID в telephony.externalCall.show.
const crmSearch = await $b24.actions.v2.call.make({
method: 'telephony.externalCall.searchCrmEntities',
params: { PHONE_NUMBER: '79062195047' },
requestId: 'crm-search',
})
const crmEntities = crmSearch.getData().result
if (!crmEntities.length) {
throw new Error('Клиент с таким телефоном не найден в CRM')
}
const assignedById = Number(crmEntities[0].ASSIGNED_BY_ID)
const reg = await $b24.actions.v2.call.make({
method: 'telephony.externalCall.register',
params: {
USER_ID: assignedById,
PHONE_NUMBER: '79062195047',
TYPE: 2,
LINE_NUMBER: 'line-1',
EXTERNAL_CALL_ID: 'asterisk-1773130778.18441-manager',
SHOW: 0,
},
requestId: 'call-register',
})
const callId = reg.getData().result.CALL_ID
await $b24.actions.v2.call.make({
method: 'telephony.externalCall.show',
params: { CALL_ID: callId, USER_ID: assignedById },
requestId: 'call-show',
})
crm_entities = client.telephony.external_call.search_crm_entities(
phone_number="79062195047",
).response.result
if not crm_entities:
raise RuntimeError("Клиент с таким телефоном не найден в CRM")
assigned_by_id = int(crm_entities[0]["ASSIGNED_BY_ID"])
reg = client.telephony.external_call.register(
phone_number="79062195047",
call_type=2,
user_id=assigned_by_id,
line_number="line-1",
external_call_id="asterisk-1773130778.18441-manager",
show=0,
).response.result
client.telephony.external_call.show(
call_id=reg["CALL_ID"],
user_id=assigned_by_id,
).response
$crmEntities = $b24->core->call('telephony.externalCall.searchCrmEntities', [
'PHONE_NUMBER' => '79062195047',
])->getResponseData()->getResult();
if (empty($crmEntities)) {
throw new \RuntimeException('Клиент с таким телефоном не найден в CRM');
}
$assignedById = (int)$crmEntities[0]['ASSIGNED_BY_ID'];
$reg = $b24->core->call('telephony.externalCall.register', [
'USER_ID' => $assignedById,
'PHONE_NUMBER' => '79062195047',
'TYPE' => 2,
'LINE_NUMBER' => 'line-1',
'EXTERNAL_CALL_ID' => 'asterisk-1773130778.18441-manager',
'SHOW' => 0,
])->getResponseData()->getResult();
$b24->core->call('telephony.externalCall.show', [
'CALL_ID' => $reg['CALL_ID'],
'USER_ID' => $assignedById,
]);
В ответе register сохраните CALL_ID. Он нужен для завершения звонка и прикрепления записи.
5. Обработаем исходящий звонок из CRM
Когда сотрудник нажимает на номер в CRM, Битрикс24 регистрирует звонок и отправляет приложению событие ONEXTERNALCALLSTART с полями CALL_ID, PHONE_NUMBER, USER_ID, LINE_NUMBER, CRM_ENTITY_TYPE, CRM_ENTITY_ID, CALL_LIST_ID.
Событие принимает ваш веб-сервер. После запуска вызова на АТС завершите тот же CALL_ID методом finish. Поле CALL_ID приходит в событии, поэтому для исходящего звонка не нужно повторно вызывать register.
import express from 'express'
const app = express()
app.use(express.urlencoded({ extended: true }))
app.post('/events', async (req, res) => {
if (req.body.event === 'ONEXTERNALCALLSTART') {
const data = req.body.data
// ... инициировать вызов на АТС по data.PHONE_NUMBER ...
// по завершении разговора:
await $b24.actions.v2.call.make({
method: 'telephony.externalCall.finish',
params: { CALL_ID: data.CALL_ID, USER_ID: data.USER_ID, DURATION: 95, STATUS_CODE: '200' },
requestId: 'call-finish',
})
}
res.send('ok')
})
from flask import Flask, request
app = Flask(__name__)
@app.post("/events")
def events():
if request.form.get("event") == "ONEXTERNALCALLSTART":
data = request.form # поля приходят как data[CALL_ID] и т.д.
# ... инициировать вызов на АТС ...
client.telephony.external_call.finish(
call_id=data.get("data[CALL_ID]"),
user_id=int(data.get("data[USER_ID]")),
duration=95,
status_code="200",
).response
return "ok"
<?php
// Обработчик события ONEXTERNALCALLSTART
if (($_REQUEST['event'] ?? '') === 'ONEXTERNALCALLSTART') {
$data = $_REQUEST['data'];
// ... инициировать вызов на АТС по $data['PHONE_NUMBER'] ...
$b24->core->call('telephony.externalCall.finish', [
'CALL_ID' => $data['CALL_ID'],
'USER_ID' => $data['USER_ID'],
'DURATION' => 95,
'STATUS_CODE' => '200',
]);
}
Обработчик должен вернуть HTTP-ответ 200. После этого Битрикс24 считает событие доставленным.
6. Завершим звонок и сохраним результат
После разговора вызовите telephony.externalCall.finish: метод скрывает карточку, сохраняет звонок в статистике и создает CRM-дело. Передайте CALL_ID, USER_ID, DURATION (сек) и STATUS_CODE (200 — успешно, 304 — пропущенный).
Если запись еще не готова, вызовите finish без записи, а позже прикрепите ее методом telephony.externalCall.attachRecord. После записи можно добавить расшифровку методом telephony.call.attachTranscription.
const finishResponse = await $b24.actions.v2.call.make({
method: 'telephony.externalCall.finish',
params: { CALL_ID: callId, USER_ID: 1270, DURATION: 95, STATUS_CODE: '200', ADD_TO_CHAT: 1 },
requestId: 'call-finish',
})
// позже, когда запись готова
await $b24.actions.v2.call.make({
method: 'telephony.externalCall.attachRecord',
params: { CALL_ID: callId, FILENAME: 'record.mp3', RECORD_URL: 'https://your-domain.example/record.mp3' },
requestId: 'attach-record',
})
finish_response = client.telephony.external_call.finish(
call_id=call_id, user_id=1270, duration=95, status_code="200", add_to_chat=1,
).response
# позже, когда запись готова
client.telephony.external_call.attach_record(
call_id=call_id, filename="record.mp3", record_url="https://your-domain.example/record.mp3",
).response
$finishResponse = $b24->core->call('telephony.externalCall.finish', [
'CALL_ID' => $callId, 'USER_ID' => 1270, 'DURATION' => 95, 'STATUS_CODE' => '200', 'ADD_TO_CHAT' => 1,
]);
// позже, когда запись готова
$b24->core->call('telephony.externalCall.attachRecord', [
'CALL_ID' => $callId, 'FILENAME' => 'record.mp3', 'RECORD_URL' => 'https://your-domain.example/record.mp3',
]);
Успешный ответ finish содержит запись статистики звонка. Поля CALL_STATUS, CALL_FAILED_CODE, CRM_ACTIVITY_ID, CRM_ENTITY_TYPE и CRM_ENTITY_ID помогают проверить, что звонок завершен и связан с CRM.
{
"result": {
"CALL_ID": "externalCall.716f1cb73def9700a23842adf9c4c568.1773130779",
"PORTAL_USER_ID": 1270,
"PHONE_NUMBER": "79062195047",
"CALL_DURATION": 95,
"CALL_STATUS": 1,
"CALL_FAILED_CODE": "200",
"CRM_ACTIVITY_ID": 7943,
"CRM_ENTITY_TYPE": "CONTACT",
"CRM_ENTITY_ID": 797,
"ID": 7
}
}
Если запись прикреплена по RECORD_URL, метод attachRecord вернет идентификатор файла.
{
"result": {
"FILE_ID": 9079
}
}
Зафиксируем звонок без показа карточки
Если связь между АТС и Битрикс24 была недоступна, после восстановления сохраните факт звонка без карточки: вызовите register с SHOW = 0, затем finish с фактическими данными. Сценарий не показывает звонок в реальном времени, но сохраняет историю, статистику и CRM-дело.
const reg = await $b24.actions.v2.call.make({
method: 'telephony.externalCall.register',
params: {
USER_ID: 1269,
PHONE_NUMBER: '79062195047',
TYPE: 2,
LINE_NUMBER: 'line-1',
EXTERNAL_CALL_ID: 'asterisk-1773130778.18441-offline',
SHOW: 0,
},
requestId: 'call-register',
})
const callId = reg.getData().result.CALL_ID
await $b24.actions.v2.call.make({
method: 'telephony.externalCall.finish',
params: { CALL_ID: callId, USER_ID: 1269, DURATION: 0, STATUS_CODE: '304' },
requestId: 'call-finish',
})
call_id = client.telephony.external_call.register(
phone_number="79062195047",
call_type=2,
user_id=1269,
line_number="line-1",
external_call_id="asterisk-1773130778.18441-offline",
show=0,
).response.result["CALL_ID"]
client.telephony.external_call.finish(
call_id=call_id, user_id=1269, duration=0, status_code="304",
).response
$callId = $b24->core->call('telephony.externalCall.register', [
'USER_ID' => 1269,
'PHONE_NUMBER' => '79062195047',
'TYPE' => 2,
'LINE_NUMBER' => 'line-1',
'EXTERNAL_CALL_ID' => 'asterisk-1773130778.18441-offline',
'SHOW' => 0,
])->getResponseData()->getResult()['CALL_ID'];
$b24->core->call('telephony.externalCall.finish', [
'CALL_ID' => $callId, 'USER_ID' => 1269, 'DURATION' => 0, 'STATUS_CODE' => '304',
]);
Проверим результат
Проверьте, что интеграция обработала звонок:
- во время входящего звонка у нужного сотрудника открылась карточка звонка
- после ответа оператора карточка скрылась у остальных сотрудников очереди
- после
finishзвонок появился в статистике телефонии - в CRM создано дело звонка, если звонок связан с контактом, компанией, лидом или сделкой
- после
attachRecordв деле звонка доступна запись разговора
Через REST успешность подтверждают поля ответа finish: ID, CALL_STATUS, CALL_FAILED_CODE, CRM_ACTIVITY_ID, CRM_ENTITY_TYPE и CRM_ENTITY_ID.
Выведите эти поля из ответа finish, который получили в шаге 6.
const finishResult = finishResponse.getData().result
console.table({
ID: finishResult.ID,
CALL_STATUS: finishResult.CALL_STATUS,
CALL_FAILED_CODE: finishResult.CALL_FAILED_CODE,
CRM_ACTIVITY_ID: finishResult.CRM_ACTIVITY_ID,
CRM_ENTITY_TYPE: finishResult.CRM_ENTITY_TYPE,
CRM_ENTITY_ID: finishResult.CRM_ENTITY_ID,
})
finish_result = finish_response.result
for field in [
"ID",
"CALL_STATUS",
"CALL_FAILED_CODE",
"CRM_ACTIVITY_ID",
"CRM_ENTITY_TYPE",
"CRM_ENTITY_ID",
]:
print(field, finish_result.get(field))
$finishResult = $finishResponse->getResponseData()->getResult();
echo 'ID: ' . $finishResult['ID'] . PHP_EOL;
echo 'CALL_STATUS: ' . $finishResult['CALL_STATUS'] . PHP_EOL;
echo 'CALL_FAILED_CODE: ' . $finishResult['CALL_FAILED_CODE'] . PHP_EOL;
echo 'CRM_ACTIVITY_ID: ' . $finishResult['CRM_ACTIVITY_ID'] . PHP_EOL;
echo 'CRM_ENTITY_TYPE: ' . $finishResult['CRM_ENTITY_TYPE'] . PHP_EOL;
echo 'CRM_ENTITY_ID: ' . $finishResult['CRM_ENTITY_ID'] . PHP_EOL;
Ошибки и диагностика
Если метод вернул ошибку, проверьте данные запроса и контекст авторизации.
|
Код или текст ошибки |
Причина и действие |
|
|
Метод вызван вне контекста приложения. Проверьте OAuth-авторизацию приложения и scope |
|
|
В |
|
|
В |
|
|
Номер клиента не распознан. Передавайте телефон в международном формате без букв |
|
|
Линия с таким |
|
|
В |
|
|
Звонок не найден или уже завершен. Проверьте, что |
|
|
Запись прикрепляется до завершения звонка. Сначала вызовите |
|
|
В |
|
|
Запись передана в неподдерживаемом формате. Используйте файл |
|
|
В |
Повторяйте сценарий с шага, на котором произошла ошибка. Если ошибка возникла после успешного register, используйте тот же CALL_ID, пока звонок не завершен.
Что важно учитывать
- Для исходящих звонков из CRM нужен установленный обработчик
ONEXTERNALCALLSTART. Входящий вебхук не получает это событие - Передавайте уникальный
EXTERNAL_CALL_IDдля каждого физического звонка, чтобы повторныйregisterне вернул уже существующийCALL_ID - Храните OAuth-токены приложения на сервере, не добавляйте их в публичный клиентский код
- Обработчик события должен быть доступен из интернета по HTTPS и принимать POST-запросы от Битрикс24
- Запись разговора прикрепляйте после
finish, когда звонок уже сохранен в статистике