Интеграция Asterisk и Битрикс24


    В сети есть разные варианты интеграции IP-АТС Asterisk и CRM Битрикс24, но мы, все таки, решили написать свою.

    По функционалу все стандартно:

    • Кликом на ссылку с номером телефона клиента в Битрикс24, Asterisk соединяет внутренний номер пользователя, от имени которого это клик совершен, с номером телефона клиента. 
В Битрикс24 фиксируется запись о звонке и по окончании вызова подтягивается запись разговора.
    • На Asterisk поступает звонок извне — в интерфейсе Битрикс24 показываем карточку клиента тому сотруднику, на номер которого этот звонок прилетел.
      Если такого клиента нет, откроем карточку создания нового лида.
      Как только звонок завершен, отражаем это в карточке и подтягиваем запись разговора.

    Под катом расскажу как все настроить у себя и дам линк на github — да-да, забирайте и пользуйтесь!

    Общее описание


    Свою интеграцию мы назвали CallMe. CallMe — это небольшое веб-приложение, написанное на PHP.

    Используемые технологии и сервисы


    • PHP 5.6
    • PHP AMI-библиотека
    • Composer
    • Nginx + php-fpm
    • supervisor
    • AMI (Asterisk menegement interface)
    • Вебхуки Bitrix (упрощенная реализация REST API)

    Предварительная настройка


    На сервере с Asterisk необходимо установить web-сервер (у нас это nginx+php-fpm), supervisor и git.

    Команда для установки (CentOS):

    yum install nginx php-fpm supervisor git

    Переходим директорию, доступную веб-серверу, тянем из гита приложение и выставляем нужные права на папку:

    
    cd /var/www
    git clone https://github.com/ViStepRU/callme.git
    chown nginx. -R callme/
    

    Далее настроим nginx, наш конфиг разместился в

    /etc/nginx/conf.d/pbx.vistep.ru.conf

    server {
    	server_name www.pbx.vistep.ru pbx.vistep.ru;
    	listen *:80;
    	rewrite ^  https://pbx.vistep.ru$request_uri? permanent;
    }
    
    server {
    #        listen *:80;
    #	server_name pbx.vistep.ru;
    
    
    	access_log /var/log/nginx/pbx.vistep.ru.access.log main;
            error_log /var/log/nginx/pbx.vistep.ru.error.log;
    
        listen 443 ssl http2;
        server_name pbx.vistep.ru;
        resolver 8.8.8.8;
        ssl_stapling on;
        ssl on;
        ssl_certificate /etc/letsencrypt/live/pbx.vistep.ru/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/pbx.vistep.ru/privkey.pem;
        ssl_dhparam /etc/nginx/certs/dhparam.pem;
        ssl_session_timeout 24h;
        ssl_session_cache shared:SSL:2m;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers kEECDH+AES128:kEECDH:kEDH:-3DES:kRSA+AES128:kEDH+3DES:DES-CBC3-SHA:!RC4:!aNULL:!eNULL:!MD5:!EXPORT:!LOW:!SEED:!CAMELLIA:!IDEA:!PSK:!SRP:!SSLv2;
        ssl_prefer_server_ciphers on;
        add_header Strict-Transport-Security "max-age=31536000;";
        add_header Content-Security-Policy-Report-Only "default-src https:; script-src https: 'unsafe-eval' 'unsafe-inline'; style-src https: 'unsafe-inline'; img-src https: data:; font-src https: data:; report-uri /csp-report";
    	
    	root /var/www/callme;
    	index  index.php;
            location ~ /\. {
                    deny all; # запрет для скрытых файлов
            }
    
            location ~* /(?:uploads|files)/.*\.php$ {
                    deny all; # запрет для загруженных скриптов
            }
    
            location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
                    access_log off;
                    log_not_found off;
                    expires max; # кеширование статики
            }
    
    	location ~ \.php {
    		root /var/www/callme;
    		index  index.php;
    		fastcgi_pass unix:/run/php/php5.6-fpm.sock;
    	#	fastcgi_pass 127.0.0.1:9000;
    		fastcgi_index index.php;
    		fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    		include /etc/nginx/fastcgi_params;
    		}
    }
    

    Разбор конфига, вопросы безопасности, получение сертификата и даже выбор web-сервера я оставлю за рамками статьи — об этом много написано. У приложения нет ограничений, оно работает и по http и по https.

    У нас — https, сертификат let's encrypt.

    Если вы все сделали правильно, то перейдя по ссылке должны увидеть нечто подобное



    Настройка Битрикс24


    Создадим два вебхука.

    Входящий вебхук.

    Под учетной записью администратора (с id 1) идем по пути: Приложения -> Вебхуки -> Добавить вебхук -> Входящий вебхук



    Заполняем параметры входящего вебхука как на скринах:





    И жмем сохранить.

    После сохранения Битрикс24 предоставит URL входящего вебхука, например:



    Сохраните себе ваш вариант URL без завершающего /profile/ — он будет использоваться в приложении для работы с входящими звонками.

    У меня это https://b24-xsynia.bitrix24.ru/rest/1/7eh61lh8pahw0fwt

    Исходящий вебхук.

    Приложения -> Вебхуки -> Добавить вебхук -> Исходящий вебхук

    Подробности снова на скринах:





    Сохраняем и получаем код авторизации



    У меня это xcrp2ylhzzd2v43cmfjqmkvrgrcbkni6. Его тоже нужно скопировать себе, он нужен для совершения исходящих звонков.

    Важно!
    На сервере Битрикс24 должен быть настроен ssl-сертификат (можно использовать letsencrypt), иначе api битрикса не будет работать. Если у вас облачная версия, можете не волноваться — там уже есть ssl.

    И последним штрихом установим наш CallMeOut в качестве приложения для совершения звонков (чтобы по клику на номер на АТС улетала команда для оригинации звонка).

    В меню выбираем: Еще -> Телефония -> Еще -> Настройки, ставим в «Номер для исходящего звонка по-умолчанию» Приложение: CallMeOut и жмем «Сохранить»



    Настройка asterisk


    Для успешного взаимодействия Asterisk и Bitrix24 нам нужно добавить AMI-пользователя callme в manager.conf:

    [callme]
    secret = JD3clEB8_f23r-3ry84gJ
    deny = 0.0.0.0/0.0.0.0
    permit = 127.0.0.1/255.255.255.0
    permit= 10.100.111.249/255.255.255.255
    permit = 192.168.254.0/255.255.255.0
    read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
    write = system,call,agent,log,verbose,user,config,command,reporting,originate
    

    Далее есть несколько хитростей, которые потребуется внедрить посредствам dialplan (у нас это extensions.ael).

    Привожу весь файл, а после дам пояснения:

    globals {
        WAV=/var/www/pbx.vistep.ru/callme/records/wav; //Временный каталог с WAV
        MP3=/var/www/pbx.vistep.ru/callme/records/mp3; //Куда выгружать mp3 файлы
        URLRECORDS=https://pbx.vistep.ru/callme/records/mp3;
        RECORDING=1; // Запись, 1 - включена.
    };
    
    macro recording(calling,called) {
            if ("${RECORDING}" = "1"){
                  Set(fname=${UNIQUEID}-${STRFTIME(${EPOCH},,%Y-%m-%d-%H_%M)}-${calling}-${called});
    	      Set(datedir=${STRFTIME(${EPOCH},,%Y/%m/%d)});
    	      System(mkdir -p ${MP3}/${datedir});
    	      System(mkdir -p ${WAV}/${datedir});
                  Set(monopt=nice -n 19 /usr/bin/lame -b 32  --silent "${WAV}/${datedir}/${fname}.wav"  "${MP3}/${datedir}/${fname}.mp3" && rm -f "${WAV}/${fname}.wav" && chmod o+r "${MP3}/${datedir}/${fname}.mp3");
    	      Set(FullFname=${URLRECORDS}/${datedir}/${fname}.mp3);
                  Set(CDR(filename)=${fname}.mp3);
    	      Set(CDR(recordingfile)=${fname}.wav);
                  Set(CDR(realdst)=${called});
                  MixMonitor(${WAV}/${datedir}/${fname}.wav,b,${monopt});
    
           };
    };
    
    
    context incoming {
    888999 => {
    	&recording(${CALLERID(number)},${EXTEN});
            Answer();
            ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp()); // выставляем CallerID если узнали его у Битрикс24
            Set(CallStart=${STRFTIME(epoch,,%s)});  
            Queue(Q1,tT);
            Set(CallMeDISPOSITION=${CDR(disposition)}); 
            Hangup();
            }
    
    h => {
        Set(CDR_PROP(disable)=true); 
        Set(CallStop=${STRFTIME(epoch,,%s)}); 
        Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)}); 
        ExecIF(${ISNULL(${CallMeDISPOSITION})}?Set(CallMeDISPOSITION=${CDR(disposition)}):NoOP(=== CallMeDISPOSITION already was set ===));  
        System(curl -s https://pbx.vistep.ru/CallMeOut.php --data action=sendcall2b24 --data call_id=${CallMeCALL_ID} --data-urlencode FullFname=${FullFname} --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition=${CallMeDISPOSITION});  
    }
    
    }
    
    
    context default {
    
    _X. => {
            Hangup();
            }
    };
    
    
    context dial_out {
    
    _[1237]XX => {
    	&recording(${CALLERID(number)},${EXTEN});
            Set(__CallIntNum=${CALLERID(num)})
    	Set(CallStart=${STRFTIME(epoch,,%s)});
            Dial(SIP/${EXTEN},,tTr);
            Hangup();
            }
    
    _11XXX => {
    	&recording(${CALLERID(number)},${EXTEN});
    	Set(CallStart=${STRFTIME(epoch,,%s)});
    	Set(__CallIntNum=${CALLERID(num)});
            Dial(SIP/${EXTEN:2}@toOurAster,,t);
            Hangup();
            }
    
    _. => {
    	&recording(${CALLERID(number)},${EXTEN});
            Set(__CallIntNum=${CALLERID(num)})
    	Set(CallStart=${STRFTIME(epoch,,%s)});
    	Dial(SIP/${EXTEN}@toOurAster,,t);
    	Hangup();
            }
    
    h => {
            Set(CDR_PROP(disable)=true);
            Set(CallStop=${STRFTIME(epoch,,%s)});
            Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)});
    	if(${ISNULL(${CallMeDISPOSITION})}) {
              Set(CallMeDISPOSITION=${CDR(disposition)});
            }
    	System(curl -s http://pbx.vistep.ru/CallMeOut.php --data action=sendcall2b24 --data call_id=${CallMeCALL_ID} --data-urlencode FullFname=${FullFname} --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition=${CallMeDISPOSITION});
    }
    
    };
    
    

    Начнем с самого начала: директива globals.

    Переменная URLRECORDS хранит в себе URL к файлам записей разговоров, по которому Bitrix24 будет их подтягивать в карточку контакта.

    Далее нам интересен макрос макрос recording.

    Здесь, помимо записи разговоров, мы установим переменную FullFname.

    Set(FullFname=${URLRECORDS}/${datedir}/${fname}.mp3);

    Она хранит полный URL к конкретному файлу (макрос вызывается везде).

    Разберем исходящий звонок:

    _. => {
    	&recording(${CALLERID(number)},${EXTEN});
            Set(__CallIntNum=${CALLERID(num)})
    	Set(CallStart=${STRFTIME(epoch,,%s)});
    	Dial(SIP/${EXTEN}@toOurAster,,t);
    	Hangup();
            }
    
    h => {
            Set(CDR_PROP(disable)=true);
            Set(CallStop=${STRFTIME(epoch,,%s)});
            Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)});
    	if(${ISNULL(${CallMeDISPOSITION})}) {
              Set(CallMeDISPOSITION=${CDR(disposition)});
            }
    	System(curl -s http://pbx.vistep.ru/CallMeOut.php --data action=sendcall2b24 --data call_id=${CallMeCALL_ID} --data-urlencode FullFname=${FullFname} --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition=${CallMeDISPOSITION});
    }
    

    Допустим мы звоним на 89991234567, первым делом попадаем сюда:

    &recording(${CALLERID(number)},${EXTEN});

    т.е. вызывается макрос записи разговора и проставляются нужные переменные.

    Далее

            Set(__CallIntNum=${CALLERID(num)})
    	Set(CallStart=${STRFTIME(epoch,,%s)});
    

    записываем кто инициировал звонок и фиксируем время старта звонка.

    И по его завершению, в специальном контексте h

    h => {
            Set(CDR_PROP(disable)=true);
            Set(CallStop=${STRFTIME(epoch,,%s)});
            Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)});
    	if(${ISNULL(${CallMeDISPOSITION})}) {
              Set(CallMeDISPOSITION=${CDR(disposition)});
            }
    	System(curl -s http://pbx.vistep.ru/CallMeOut.php --data action=sendcall2b24 --data call_id=${CallMeCALL_ID} --data-urlencode FullFname=${FullFname} --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition=${CallMeDISPOSITION});
    }
    

    отключаем запись в таблицу CDR для этого экстеншена (не нужно оно там), выставляем время завершения звонка, вычисляем продолжительность, если результат звонка не известен — ставим (переменная CallMeDISPOSITION) и, последним шагом, шлем все битриксу через системный curl.

    И еще немного магии — входящий звонок:

    888999 => {
    	&recording(${CALLERID(number)},${EXTEN});
            Answer();
            ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp()); // выставляем CallerID если узнали его у Битрикс24
            Set(CallStart=${STRFTIME(epoch,,%s)}); // начинаем отсчет времени звонка
            Queue(Q1,tT);
            Set(CallMeDISPOSITION=${CDR(disposition)}); 
            Hangup();
            }
    

    Здесь нас интересует только одна строка.

    ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp());

    Она говорит АТС установить CallerID(name) равным переменной CallMeCallerIDName.

    Сама переменная CallMeCallerIDName, в свою очередь, устанавливается приложением CallMe (если в Bitrix24 есть ФИО для номера позвонившего — установим в качестве CallerID(name), нет — ничего не будем делать).

    Настройка приложения


    Файл настроек приложения — /var/www/pbx.vistep.ru/config.php

    Описание параметров приложения:

    • CallMeDEBUG — если 1, то в лог файл будут писаться все события, обрабатываемые приложением, 0 — ничего не пишем
    • tech — SIP/PJSIP/IAX/etc
    • authToken — токен авторизации битрикс24, код авторизации исходящего вебхука
    • bitrixApiUrl — URL входящего вебхука, без profile/
    • extentions — список внешних номеров
    • context — контекст для оригинации звонка
    • listener_timeout — скорость обработки событий от asterisk
    • asterisk — массив с настройками подключения к астериску:
    • host — ip или hostname сервера астериск
    • scheme — схема подключения (tcp://, tls://)
    • port — порт
    • username — имя пользователя
    • secret — пароль
    • connect_timeout — таймаут подключения
    • read_timeout — таймаут чтения

    пример файла настроек:

     <?php
    return array(
    
            'CallMeDEBUG' => 1, // дебаг сообщения в логе: 1 - пишем, 0 - не пишем
            'tech' => 'SIP',
            'authToken' => 'xcrp2ylhzzd2v43cmfjqmkvrgrcbkni6', //токен авторизации битрикса
            'bitrixApiUrl' => 'https://b24-xsynia.bitrix24.ru/rest/1/7eh61lh8pahw0fwt', //url к api битрикса (входящий вебхук)
            'extentions' => array('888999'), // список внешних номеров, через запятую
            'context' => 'dial_out', //исходящий контекст для оригинации звонка
            'asterisk' => array( // настройки для подключения к астериску
                        'host' => '10.100.111.249',
                        'scheme' => 'tcp://',
                        'port' => 5038,
                        'username' => 'callme',
                        'secret' => 'JD3clEB8_f23r-3ry84gJ',
                        'connect_timeout' => 10000,
                        'read_timeout' => 10000
                    ),
            'listener_timeout' => 300, //скорость обработки событий от asterisk
    
    );

    Настройка supervisor


    Supervisor служит для запуска процесса-обработчика событий от Asterisk CallMeIn.php, который отслеживает входящие звонки и взаимодействует с Битрикс24 (показать карточку, скрыть карточку и т.д.).

    Файл настроек, который необходимо создать:

    /etc/supervisord.d/callme.conf

    [program:callme]
    command=/usr/bin/php CallMeIn.php
    directory=/var/www/pbx.vistep.ru
    autostart=true
    autorestart=true
    startretries=5
    stderr_logfile=/var/www/pbx.vistep.ru/logs/daemon.log
    stdout_logfile=/var/www/pbx.vistep.ru/logs/daemon.log

    Запуск и рестарт приложения:

    supervisorctl start callme
    supervisorctl restart callme

    просмотр статуса работы приложения:

    supervisorctl status callme
    callme                           RUNNING   pid 11729, uptime 17 days, 16:58:07

    Заключение


    Получилось достаточно сложно, но уверен — опытный администратор сумеет внедрить у себя и порадовать своих пользователей.

    Как обещал, линк на гитхаб.

    Вопросы, пожелания — прошу в комменты. Также если интересно как шла разработка этой интеграции, напишите, а в очередной статье я постараюсь раскрыть все более детально.
    Поделиться публикацией
    Комментарии 10
      –1
      Немного не по теме, но увидев статью про этот «продукт» не смог сдержатся. Конечно компания 1с в наше время это гигант (или динозавр :)). Но работая в Корпоративном Битрикс24 я просто ненавижу Ваш мессенджер (и не только) за невозможность использования буфера обмена для вставки объектов, а чтобы в задачу перекинуть часть диалога с объектами… там вообще легче переписать весь диалог вручную. А с Вами оказывается интеграции еще делают:)
      ps Если конечно это возможно (при супер настройке-прошу прощения, но Ваши «настройщики за деньги» — сказали что нельзя. И мы тоже не нашли...)
        0
        Уважаемый, а при чем тут мы?
        Наша команда с Битриксом никак не связана.

        И, кстати, вебхуки — реально классная тема.
        0
        Я правильно понимаю, звонок прилетает в (*), в Битрикс24 поднимается карточка клиента и все такое? Или просто звонилка с Битрикс24?
          +3
          Правильно понимаете, не просто звонилка.
          Исходящие:
          клик на номер, на * улетает команда позвонить на внутренний номер того, кто кликнул. как только тот кто кликнул поднимает трубку, начинается звонок на номер, на который он кликнул и их соединяет

          Входящие:
          приходит звонок, какими-то внутренними средствами * адресуется конкретному внутреннему номеру — в битриксе показываем карточку, нет такого клиента — даем создать нового
          Если звонок гуляет от одного внутреннего к другому, то будем показывать всем по очереди — звенит на 123 — показываем Васе, переходит на 345 — у Васи убираем и показываем Пете.
            0
            Попробую на досуге, спасибо.
          0
          Astrisk* — наверно имелось ввиду asterisk?

          По существу вопрос — почему php не 7.0?
            0
            Спасибо, исправил.
            Не было уверенности, что github.com/marcelog/PAMI работает с 7.0, а проверять и не стали.
              0
              работает с 7.1
            +2
            Очень интересно. Не подозревал о существовании вебхуков в Битрикс24 и что с помощью их стандартного функционала можно так просто сделать интеграцию.
            Мы реализовали похожий механизм, только использовали вебсокет для рассылки уведомлений о входящих/исходящих звонках и небольшой API на стороне АТС для вызова номера из Битрикса. В этом случае, конечно, нужно еще свой модуль писать, который будет подключаться к вебсокету и слушать уведомления, но не ограничивает интеграцию одним лишь Битриксом.
              +1
              Уже больше года как они добавили вебхуки.
              С ними действительно все достаточно просто.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое