Integration of Asterisk and Bitrix24

Integration of Asterisk and Bitrix24
The network has different options for integrating IP-PBX Asterisk and CRM Bitrix24, but we, nevertheless, decided to write our own.

Functionally, 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 behalf of whom this click was made with the client's phone number. In Bitrix24, a record of the call is recorded, and at the end of the call, the conversation record is pulled up.
  • A call arrives at Asterisk from outside - in the Bitrix24 interface, we show the client 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 pull up the recording of the conversation.

Under the cut, I’ll tell you how to set everything up for yourself and give a link to github - yes, 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
  • Compose
  • nginx + php fpm
  • supervisor
  • AMI (Asterisk Manager Interface)
  • Bitrix webhooks (simplified REST API implementation)

presetting

On the 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 pass the directory available to the web server, pull the application from the git 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 choosing a web server outside the scope of the article - a lot has been written about this. The application has no restrictions, it works on both http and https.

We have https, let's encrypt certificate.

If you did everything correctly, then by clicking on the link you should see something like this

Integration of Asterisk and Bitrix24

Configuring Bitrix24

Let's create two webhooks.

Incoming webhook.

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

Integration of Asterisk and Bitrix24

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

Integration of Asterisk and Bitrix24

Integration of Asterisk and Bitrix24

And click save.

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

Integration of Asterisk and Bitrix24

Save your version of the URL without the trailing /profile/ - it will be used in the application to work with incoming calls.

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

Outgoing webhook.

Applications -> Webhooks -> Add Webhook -> Outgoing Webhook

Details are on the screenshots:

Integration of Asterisk and Bitrix24

Integration of Asterisk and Bitrix24

Save and get the authorization code

Integration of Asterisk and Bitrix24

I have it xcrp2ylhzzd2v43cmfjqmkvrgrcbkni6. You also need to copy it to yourself, it is needed to make outgoing calls.

Important!

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

Important!

In the "Processor's address" field, an address accessible from the Internet must be indicated!

And with the final touch, let's install our CallMeOut as an application for making calls (so that by clicking on the number on the PBX, a command will fly to originate the call).

In the menu, select: More -> Telephony -> More -> Settings, put in the "Number for outgoing calls by default" Application: CallMeOut and click "Save"

Integration of Asterisk and Bitrix24

asterisk setup

For successful interaction between 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 will need to be implemented through dialplan (we have extensions.ael).

I'll quote the whole file, and then I'll give explanations:

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: directive globals.

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

Next, we are interested in macro macro recording.

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

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 call recording macro is called and the necessary variables are set.

Next

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

we record who initiated the call and record 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 entry in the CDR table for this extension (it is not needed there), set the end time of the call, calculate the duration, if the result of the call is not known - set (variable Call Me DISPOSITION) and, 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 says PBX install CallerID(name) variable CallMeCallerIDName.

The CallMeCallerIDName variable itself, in turn, is set by the CallMe application (if Bitrix24 has a full name for the caller's number, we will 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 - we 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/
  • extensions β€” list of external numbers
  • context β€” context for call origination
  • listener_timeout - event processing speed from asterisk
  • asterisk - an array with the connection settings to the asterisk:
  • host - ip or hostname of the asterisk server
  • scheme β€” connection diagram (tcp://, tls://)
  • port - port
  • username - Username
  • secret - password
  • connect_timeout - connection timeout
  • read_timeout - read timeout

example settings file:

 <?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 setup

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

Settings file to create:

/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

Starting and restarting the application:

supervisorctl start callme
supervisorctl restart callme

view the status of the application:

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

Conclusion

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

As promised, link to github.

Questions, suggestions - please in the comments. Also, if you are interested in how the development of this integration went, write, and in the next article I will try to reveal everything in more detail.

Source: habr.com

Add a comment