Asterisk and Bitrix24 Integration


    There are various options for integrating Asterisk IP-PBX and Bitrix24 CRM on the network, but we decided to write our own.

    By functionality, everything is standard:

    • By clicking on the link with the client’s phone number in Bitrix24, Asterisk connects the internal number of the user on whose behalf the click is committed, with the client’s phone number. In Bitrix24, a call record is recorded and, at the end of the call, the conversation record is tightened.
    • A call comes from outside Asterisk - in the Bitrix24 interface we show the client’s card to the employee to whose number this call arrived.
      If there is no such client, open the card for creating a new lead.
      As soon as the call is completed, we reflect this in the card and tighten the recording of the conversation.

    Under the cat I’ll tell you how to set everything up for myself and give a link on github - yes, take it and use it!

    general description


    We called our integration CallMe. CallMe is a small web application written in PHP.

    Used technologies and services


    • PHP 5.6
    • PHP AMI Library
    • Composer
    • Nginx + php-fpm
    • supervisor
    • AMI (Asterisk menegement interface)
    • Bitrix Webhooks (Simplified REST API Implementation)

    Preset


    On a server with Asterisk, you need to install a web server (we have nginx + php-fpm), supervisor and git.

    Installation command (CentOS):

    yum install nginx php-fpm supervisor git

    We go to the directory accessible to the web server, pull the application from the gita and set the necessary rights to the folder:

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

    Next, configure nginx, our config is located in

    /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;
    		}
    }
    

    I will leave the analysis of the config, security issues, obtaining a certificate and even the choice of a web server outside the scope of the article - much has been written about this. The application has no restrictions, it works on http and https.

    We have https, let's encrypt certificate.

    If you did everything correctly, then following the link you should see something similar



    Configuring Bitrix24


    Let's create two webhooks.

    Incoming webhook.

    Under the administrator account (with id 1) we go along the path: Applications -> Webhooks -> Add webhook -> Incoming webhook



    Fill in the parameters of the incoming webhook as on the screenshots:





    And click save.

    After saving, Bitrix24 will provide the incoming webhook URL, for example:



    Save your version of the URL without the ending / profile / - it will be used in the application for working with incoming calls.

    I have this https://b24-xsynia.bitrix24.ru/rest/1/7eh61lh8pahw0fwt/

    outgoing webhook.

    Applications -> Webhooks -> Add webhook -> Outgoing webhook

    Details again on the screenshots:





    Save and get the authorization code



    I have it xcrp2ylhzzd2v43cmfjqmkvrgrcbkni6. It also needs to be copied to itself, it is needed to make outgoing calls.

    Important!
    An SSL certificate must be configured on the Bitrix24 server (you can use letsencrypt), otherwise the Bitrix api will not work. If you have a cloud version, don’t worry - ssl is already there.


    Important!
    In the field "Handler address" should be indicated accessible from the Internet address!

    And with the final touch we’ll install our CallMeOut as an application for making calls (so that by clicking on the number on the PBX a team will fly off to make the call).

    In the menu, select: More -> Telephony -> More -> Settings, put in “Default outgoing call number” Application: CallMeOut and click “Save”



    Configure asterisk


    For the successful interaction of Asterisk and Bitrix24, we need to add the callme AMI user to 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
    

    Next, there are a few tricks that you will need to implement using dialplan (we have extensions.ael).

    I bring the whole file, and after I give an explanation:

    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 ===));  
    }
    }
    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});
    }
    };
    

    Let's start from the beginning: the globals directive .

    The URLRECORDS variable stores the URL to the conversation recording files, according to which Bitrix24 will pull them into the contact card.

    Next, we are interested in the macro macro recording .

    Here, in addition to recording conversations, we will set the FullFname variable .

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

    It stores the full URL to a specific file (the macro is called everywhere).

    Let's analyze the outgoing call:

    _. => {
    	&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});
    }
    

    Let's say we call 89991234567, the first thing we get here:

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

    those. The conversation recording macro is called and the necessary variables are added.

    Further

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

    We record who initiated the call and fix the start time of the call.

    And upon its completion, in a special context 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});
    }
    

    turn off the record in the CDR table for this extension (you don’t need it there), set the time to end the call, calculate the duration, if the result of the call is not known - set it ( CallMeDISPOSITION variable ) and, with the last step, send everything to bitrix through the system curl.

    And a little more magic - an incoming call:

    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();
            }
    

    Here we are only interested in one line.

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

    She tells the PBX to set CallerID (name) to the variable CallMeCallerIDName .

    The CallMeCallerIDName variable itself, in turn, is set by the CallMe application (if in Bitrix24 there is a full name for the number of the caller, set it as CallerID (name) , no, we will not do anything).

    Application Setup


    Application settings file - /var/www/pbx.vistep.ru/config.php

    Description of application parameters:

    • CallMeDEBUG - if 1, then all events processed by the application will be written to the log file, 0 - do not write anything
    • tech - SIP / PJSIP / IAX / etc
    • authToken - Bitrix24 authorization token, outgoing webhook authorization code
    • bitrixApiUrl - URL of the incoming webhook, without profile /
    • extentions - list of external numbers
    • context - context for the original call
    • listener_timeout - event processing speed from asterisk
    • asterisk - an array with asterisk connection settings:
    • host - ip or hostname of the asterisk server
    • scheme - connection scheme (tcp: //, tls: //)
    • port - port
    • username - username
    • secret - password
    • connect_timeout - connection timeout
    • read_timeout - read timeout

    example configuration file:

      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 setup


    Supervisor is used to start the event handler process from Asterisk CallMeIn.php, which monitors incoming calls and interacts with Bitrix24 (show the card, hide the card, etc.).

    Settings file to be created:

    /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

    Launch and restart the application:

    supervisorctl start callme
    supervisorctl restart callme

    viewing the status of the application:

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

    Conclusion


    It turned out to be quite difficult, but I’m sure that an experienced administrator will be able to implement and please his users at home.

    As promised, link to github .

    Questions, wishes - please comment. Also, if you are interested in how the development of this integration was going on, write, and in the next article I will try to disclose everything in more detail.

    Also popular now: