Integrations Development

Widgets in the digital pipeline

Widgets can interact with the functionality of the digital pipeline and respond to any of the following events:

  • Incoming email
  • Incoming call
  • Incoming chat message
  • Going to the stage
  • Entering the site (for this event you can configure the pending action)

foto

To interact the widget with a digital funnel, in the manifest.json file, you must specify the scope of the digital_pipeline and the dp / settings block, for more information about the widget structure, see here. The endpoint digital_pipeline must be present in the widget.php file.

When a configured event arrives at endpoint digital_pipeline, the system sends a request containing the data array of the following structure

[
    'event' => [
        'type' => 15,
            'type_code' => 'lead_appeared_in_status'
                'data' => [
                    'id'          => 123124, // lead id
                    'element_type' => 2, // Type of element (2 - lead, 12 - buyer)
                    'status_id'   => 654324, // status of a lead
                    'pipeline_id' => 654324, // pipeline of a lead
                ],
                'time' => 1491300016,
    ],
    'action' => [
        'settings' => [
            'widget' => [
                 'settings' => [
                     // Settings of a widget dp
                 ]
            ]
        ]
    ],
    'kommouser' => 'example@kommo.com',
    'kommohash' => 'c123ae456cd7891246bffb1e654abb9d',
            ]

List of possible values of type and type_code

Code Type of code Description
1 lead_added Add a lead
14 lead_status_changed Change of the status of the lead
15 lead_appeared_in_status Change the status inside the widget’s extended action into several stages*
16 mail_in Incoming email
17 call_in Incoming call
18 site_visit Visiting the site
19 chat Incoming chat message
20 customer_added Adding a customer
21 customer_period_changed Change of the status of the buyer
22 customer_appeared_in_period Change the status inside the widget’s extended action into several stages*

Integration with the API

More information about adding leads through the API with a unique visitor ID can be found on this page.

*In that case, when you specify action_multiple – true in the block dp of your manifest.json, then you allow to stretch (set) the action of your widget at once to several stages. If you change the status of the lead / buyer, in the area of the action of your widget stretched to several stages, type_code = 15 will come. At the same time, when the lead / buyer moves to the status in which your widget is activated, type_code = 14 will initially come.

Exchange of digital pipeline data with a widget

In the previous section, we talked about the need to have the endpoint digital_pipeline in the back-end file of your widget.php widget. This is necessary so that when a certain event occurs, your widget can receive information from the digital pipline. We exchange data from our side and your widget by sending notifications about events using WebHook technology (for more details on the technology, see here).

Note that the WebHook from the digital pipeline is sent to the web server only once when the specified event occurs.

In the example, we show an implementation of endpoint functions and methods for working with WebHooks, for a simple widget that sends a text message when a certain event occurs.

Example of integration

protected function endpoint_digital_pipeline() {
    foreach ($this->keys as $key) {
      $this->_request[$key] = $this->check_request($key);
    }
    if (empty($this->_request['action']['settings']['widget']['settings'])) {
      throw new \Exception('Empty widget settings');
    }
    $this->_request['settings'] = $this->_request['action']['settings']['widget']['settings'];
    if (empty($this->_request['event']['data']['id'])) {
      throw new \Exception('Empty entity id');
    }
    if (!in_array($this->_request['event']['type_code'], $this->_avalible_events)) {    // Check if there is a code in the request events from a certain array of valid/selected events (_avalible_events) to trigger the widget
      return;
    }
    $this->parse_data();
    $this->init_curl();
    $this->_response = $this->curl_post(self::/* link to your API */ . $this->api_methods['your_widget_hook'], $this-
>_your_widget_hook,
'X-AMOCRM-REQUEST:Y', FALSE);
    if ((int)$this->_response['code'] === 0) {
      $this->customers_notes_set($this->_request['event']['data'], $this->_request['settings']['message']);
      curl_close($this->_curl);
      return TRUE;
    } else {
      // error message to the user
      $error_message = "Error: " . var_export($this->_response, TRUE);
      $this->customers_notes_set($this->_request['event']['data'], $error_message, FALSE, (int)$this->_response['code']);
      curl_close($this->_curl);
      throw new Helpers\Exception($error_message);
    }
  }

Receiving data from the WebHook digital pipeline

private function check_request($key) {
  return Helpers\Route::param($key) ? Helpers\Route::param($key) : NULL;  // We return either an array or NULL
}

Convert the data that came with WebHook

private function parse_data() {
    $this->_lifepay_hook = [
      'apikey' => self::/* Key of your API */,
      'data'   => json_encode([
        [
          'customerId' => $this->_request['event']['data']['id'],
          'message'    => $this->_request['settings']['message'],
        ],
      ]),
    ];
  }

Sending WebHook

private function curl_post($link, $data, $http_header = 'Content-Type: application/json', $is_json = TRUE) {
  curl_setopt($this->_curl, CURLOPT_URL, $link);
  if ($is_json) {
    $post_fields = json_encode($data);
  } else {
    $post_fields = http_build_query($data);
  }
  curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $post_fields);
  curl_setopt($this->_curl, CURLOPT_HTTPHEADER, [$http_header]);
  $response = json_decode(curl_exec($this->_curl), TRUE);
  if (curl_errno($this->_curl)) {
    $response = 'cURL error: ' . curl_error($this->_curl);
  }
  return $response;
}

Initializing and Configuring cURL

private function init_curl() {
    $this->_curl = curl_init();
    curl_setopt($this->_curl, CURLOPT_POST, TRUE);
    curl_setopt($this->_curl, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($this->_curl, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT, 2);
    curl_setopt($this->_curl, CURLOPT_TIMEOUT, 2);
    curl_setopt($this->_curl, CURLOPT_FOLLOWLOCATION, TRUE);
    curl_setopt($this->_curl, CURLOPT_HEADER, FALSE);
    curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, TRUE);
  }

Automatic setting block

When your widget is successfully added and is available for integration, access to its settings will be possible from several areas. First of all, access to the full configuration will be possible standardly, as for all integrations, from the Settings -> Integration section of your account. In the event that your widget is integration with the digital pipeline, then it will be accessed from the settings of the digital pipeline, section leads in the area of declaring automatic actions for all leads.

foto

This element is drawn by us, including the logo and the choice of the condition by which the action of your widget will be executed. On your part, you need to fill the element with quick settings or select actions that will be performed when the user chooses the condition.

For an example, let’s describe the front-end part of the widget in script.js, which displays the settings inside the quick setup element. Select the sending of the message, upon the occurrence of any condition selected by the user (see screenshot above).

Example:

dpSettings: function () {
                var w_code = self.get_settings().widget_code,   //The widget code specified in manifest.json
                    lang = self.i18n('settings'),
                    dp_modal = $(".digital-pipeline__short-task_widget-style_"+w_code)  //due to the substitution
code(w_code) of your
widget, we can refer to the element containing exactly your widget
                        .parent()
                        .parent()
                        .find('[data-action=send_widget_hook]'),
                    message_label = dp_modal.find('[title^='+lang.message.split(" ")[0]+']'),   //Your explanations to the fields,
described in
com.json
                    message_label_new = "
” + lang.message + “

“, message_input = dp_modal.find(‘input[name=message]’), //The reference to the entered text message_textarea = self.render( //Drawing the text input field {ref: ‘/tmpl/controls/textarea.twig’}, { id: ‘dp_message’, style: {‘width’: ‘396px’, ‘margin-top’: ‘5px’, ‘margin-bottom’: ‘-3px’}, value: message_input.val(), placeholder: lang.message } ); message_label.hide().after(message_label_new); message_input.hide().after(message_textarea); return true; }

It’s important to remember about declaring the settings in manifest.json, for more information about the widget structure here

"locations": [
    "settings",
    "digital_pipeline"
  ],
"dp": {
    "settings": {
      "message": {
        "name": "settings.message",
        "type": "text",
        "required": true
      }
    }

Macros

Macros are used to automatically substitute data from entity cards into text messages. Thanks to the presence of macros in the integration, you can create universal text messages for a whole list of contacts, without the time spent on substituting data in each individual case.

foto

An example of the implementation of macros from the combat script of the widget Prostor SMS from the company Axelerator, to substitute some information about the contact:

Example of integration:

/* widget.php, back-end part */
/* Declaring an array of macros */
private $macroses = array("{{contact_name}}", "{{contact_phone}}", "{{contact_email}}");
/* We receive and process the data for further use in the substitution. Please note that the extracted data
must
match declared macros to avoid empty substitutions  */
protected function endpoint_digital_pipeline()
  {
    $this->GetData();
    $account = $this->account->current();
    $contact = $this->getContactById($lead["leads"][0]["main_contact_id"]);
$contact_phone = "";
    $contact_email = "";
    foreach($contact["contacts"][0]["custom_fields"] as $field){
      if($field["code"] == "PHONE"){
        $contact_phone = $field["values"][0]["value"];
      }
      if($field["code"] == "EMAIL"){
        $contact_email = $field["values"][0]["value"];
      }
    }
/* The variable $text will contain the finished text, with the replaced variables for the data */
  $text = $this->replaceWord($this->action['settings']['widget']['settings']['message'], $contact["contacts"][0]
["name"],$contact_phone,
$contact_email);
/* The function of permutation of markers in the text on previously received data */
private function replaceWord($message, $contact_name, $contact_phone, $contact_email)
  {
foreach($this->macroses as $macros){
      if($macros == "{{contact_name}}"){
        $message = str_replace($macros, $contact_name, $message);
      }
if($macros == "{{contact_phone}}"){
        $message = str_replace($macros, $contact_phone, $message);
      }
      if($macros == "{{contact_email}}"){
        $message = str_replace($macros, $contact_email, $message);
      }
    return $message;
  }
/* Function of extracting the data of the element of the entity we are interested in (contact) */
private function getContactById($id) {
    $account = $this->account->current();
    $link = 'https://'.$account['subdomain'].'.kommo.com/private/api/v2/json/contacts/list?id[]='.$id;
    $curl=curl_init();
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_USERAGENT, 'amoCRM-API-client/1.0');
    curl_setopt($curl, CURLOPT_URL, $link);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_COOKIEFILE, dirname(__FILE__).'/cookie.txt');
    curl_setopt($curl, CURLOPT_COOKIEJAR, dirname(__FILE__).'/cookie.txt');
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
    $out=curl_exec($curl);
    $code=curl_getinfo($curl,CURLINFO_HTTP_CODE);
    curl_close($curl);
    $response=json_decode($out,true);
    $response=$response['response'];
    return $response;
  }
private function GetData()
  {
    $event = \Helpers\Route::param("event");
    $this->event = $event;
    $action = \Helpers\Route::param("action");
    $this->action = $action;
    $kommouser = \Helpers\Route::param("kommouser");
    $this->kommouser = $kommouser;
    $kommohash = \Helpers\Route::param("kommohash");
    $this->kommohash = $kommohash;
  }

Next, you need to add the ability to specify macros in the message text in the quick setup area when you connect the widget in automatic actions for all leads in the digital pipeline.

/* script.js, front-end part */
dpSettings: function () {
  w_code = self.get_settings().widget_code;
  $('.digital-pipeline__edit-bubble').css({'width': '500px'});
  var message_input = $(".digital-pipeline__short-task_widget-style_" +
    w_code).parent().parent().find("input[name=message]");
  var message_input_label = message_input.parent().parent();
  var message_textarea = self.render(
    {ref: '/tmpl/controls/textarea.twig'},
    {
      id: 'dp_message',
      value: message_input.val()
    }
  );
  var dp_marker = '&ltdiv class="marker-list__block dp_marker_block"&gt' +
    '&ltp&gtContact&lt/p&gt' +
    '&ltp class="marker-list__row"&gt&ltspan class="marker-list__tag-bot dp_marker_label" data-marker="{{contact_name}}"&gt&ltspan
class="marker-list__tag"&gt{{contact_name}}&lt/span&gt&lt/span&gt&ltspan class="marker-list__tag-descr"&gt - Name of contact&lt/span&gt&lt/p&gt' +
  '&ltp class="marker-list__row"&gt&ltspan class="marker-list__tag-bot dp_marker_label" data-marker="{{contact_phone}}"&gt&ltspan
class="marker-list__tag"&gt{{contact_phone}}&lt/span&gt&lt/span&gt&ltspan class="marker-list__tag-descr"&gt - Phone number of a contact&lt/span&gt&lt/p&gt' +
  '&ltp class="marker-list__row"&gt&ltspan class="marker-list__tag-bot dp_marker_label" data-marker="{{contact_email}}"&gt&ltspan
class="marker-list__tag"&gt{{contact_email}}&lt/span&gt&lt/span&gt&ltspan class="marker-list__tag-descr"&gt - Email of contact&lt/span&gt&lt/p&gt'
  return true;
}

Main Contact

In the event that in your integration there is a function of sending out any information, then it may be useful for you to send information only to the main contact. In entities, the lead and the buyer can have more than one contact, so the implementation of the function of sending information only to the main contact can be useful to users.

foto

For example, one of the options for implementing this feature, for simple integration with a digital pipeline, which sends text messages.

Example of integration

protected function endpoint_digital_pipeline() {
        $model_type = intval(\Helpers\Route::param('event')['data']['element_type']);
        $model_id = intval(\Helpers\Route::param('event')['data']['id']);
        $message_text = \Helpers\Route::param('action')['settings']['widget']['settings']['message'];
        $only_main = false;     //By default, the send to main contact feature will be turned off
        if (!empty(\Helpers\Route::param('action')['settings']['widget']['settings']['only_main'])) {   //If chekbox not
empty,
assign true
            $only_main = in_array(\Helpers\Route::param('action')['settings']['widget']['settings']['only_main'], ['on',
TRUE, '1']);
        }
        $this->event($model_type, $model_id, $message_text, $only_main);
    }
/* Sending the received contacts to an array */
private function event($model_type, $model_id, $message_text, $only_main = false){
        $contacts = $this->getContacts($model_type, $model_id, $only_main);
        foreach ($contacts as $contact) {
            $this->sendMessage($message_text, $contact);    //sendMessage is your message sending function
        }
    }
/* Get contacts function */
private function getContacts($model_type, $model_id, $only_main = false) {
        $contacts = [];
        $links = null;
        switch ($model_type) {  //A conditional construct for defining the type of an entity: a transaction (element_type = 2) or
the customer
(element_type = 12)
            case 2:
                $links = $this->contacts->links(['deals_link' => $model_id]);
                break;
            case 12:
                $links = $this->contacts->links(['customers_link' => $model_id]);
                break;
        }
        if (!$links) {
            return $contacts;
        }
        $contact_ids = [];
        foreach ($links as $link) {
            $contact_ids[] = intval($link['contact_id']);
            if ($only_main === true) {      //If the user has selected the mailing only to the main contact, after the first
iteration of the loop -
break. Thus, in the $contact_ids array, only the id of the primary contact
                break;
            }
        }
        if (empty($contact_ids)) {
            return $contacts;
        }
        $contacts = $this->contacts->get(['id' => $contact_ids]);
        return $contacts;
    }

Next, you need to add the ability to select a distribution only to the main contact in the quick setup area, when you connect the widget in automatic actions for all leads in the digital pipeline.

/* script.js, front-end part */
dpSettings: function () {
  var lang = self.i18n('dp.settings');
  var form = $('#widget_settings__fields_wrapper');
  var field_divs = form.find('.widget_settings_block__input_field');
  var textarea_div = field_divs.first();
  textarea_div.html('&lttextarea name="message" ' +
    'style="height:50px; width: 100%;" ' +
    'id="message" ' +
    'class="text-input text-input-textarea digital-pipeline__add-task-textarea textarea-autosize task-edit__textarea"&gt'
    +
    ''+ textarea_div.find('input').val() +
    '&lt/textarea&gt');
  var checkbox_template = '&ltlabel class="control-checkbox"&gt' +
    '&ltdiv class="control-checkbox__body"&gt' +
    '&ltinput type="checkbox" id=""/&gt' +
    '&ltspan class="control-checkbox__helper"&gt&lt/span&gt' +
    '&lt/div&gt' +
    '&ltdiv class="control-checkbox__text element__text"&gt' +
    '&ltspan class="control-checkbox__note-text"&gt' + lang.only_main.name + '&lt/span&gt' +
    '&lt/div&gt' +
    '&lt/div&gt' +
    '&lt/label&gt';
  var checkbox_div = field_divs.last();
  checkbox_div.siblings().html('');
  checkbox_div.html(checkbox_template);
  return true;
}

Logging

In the event that, based on the results of your widget, you need to enter the appropriate notification information in the entity card, then we recommend using the addition of a system event. For more information about events and their types, see here.

Events are displayed in the cards along with the tasks, always in chronological order.

foto

To add a system event, you must specify note_type = 25.

In the required text field, JSON is assigned in which two fields are indicated: text and service.

Example of integration

/* Example of request to add a system message to the lead card */
$subdomain='example';
$data['add'] = array(
    array(
        'element_id'=>2789651,
        'element_type'=>2,  //lead - 2, customer - 12
        'note_type'=>25,
        'params'=>array('text' => 'Message successfully added', 'service' => 'Your Service Name')
    ));
$link='https://'.$subdomain.'.kommo.com/api/v2/notes';
$curl=curl_init();
curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-API-client/1.0');
curl_setopt($curl,CURLOPT_URL,$link);
curl_setopt($curl,CURLOPT_CUSTOMREQUEST,'POST');
curl_setopt($curl,CURLOPT_POSTFIELDS,json_encode($data));
curl_setopt($curl,CURLOPT_HTTPHEADER,array('Content-Type: application/json'));
curl_setopt($curl,CURLOPT_HEADER,false);
curl_setopt($curl,CURLOPT_COOKIEFILE,dirname(__FILE__).'/cookie.txt'); #PHP>5.3.6 dirname(__FILE__) -> __DIR__
curl_setopt($curl,CURLOPT_COOKIEJAR,dirname(__FILE__).'/cookie.txt'); #PHP>5.3.6 dirname(__FILE__) -> __DIR__
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,0);
curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,0);
$out=curl_exec($curl);
$code=curl_getinfo($curl,CURLINFO_HTTP_CODE);
$code=(int)$code;
$errors=array(
    301=>'Moved permanently',
    400=>'Bad request',
    401=>'Unauthorized',
    403=>'Forbidden',
    404=>'Not found',
    500=>'Internal server error',
    502=>'Bad gateway',
    503=>'Service unavailable'
);
try
{
    #If the response code is not 200 or 204, we return an error message
  if($code!=200 && $code!=204)
        throw new Exception(isset($errors[$code]) ? $errors[$code] : 'Undescribed error',(int)$code);
}
catch(Exception $E)
{
    die('Error: '.$E->getMessage().PHP_EOL.'Error code: '.$E->getCode());
}

Salesbot connection

In Kommo there is an opportunity to connect, already implemented, salesbot’a. This bot can be programmed to perform certain actions. It helps to receive data from users via messengers (Telegram, Facebook Messenger, Viber).

Detailed instructions on connecting, functionality, configuration, language and working with our merchant bot in the Salesbot section

Exchange information with external web servers

Each account in Kommo at the advanced tariff and above has the ability to report on the actions to your web server. These “WebHooks” can be used to update information about leads in your store, send sms notifications or automate leads. Each WebHook can be configured for a specific operation and events.

WebHooks is the notification of third-party applications by sending notifications of events that occurred in Kommo. For more information on the operation of WebHooks and the digital pipeline, see WebHook