Пример по шагам

В данном разделе мы рассмотрим основной функционал любой интеграции в API Чатов, а также разберем остальной функционал,
который интеграция может реализовать по необходимости.

Мы разберем в этой статье как создать самую простую, но рабочую интеграцию с API Чатов, а также функции,
которые могут быть полезны для больших, тиражируемых решений.

Любая интеграция, для корректной работы с API чатов, должна реализовать следующий функционал:

Следующие функции будут уже расширением базового функционала интеграции:

В данной статье будут представлены примеры кода на PHP.
Все примеры можно скачать по ссылке.
Для уменьшения дублирования кода представим,
что у нас уже есть файл со вспомогательными методами helpers.php:

<?php

/**
 * Расчитываем хэш тела запроса
 * @param string $body Тело запроса в строковом представлении (json)
 *
 * @return string
 */
function createBodyChecksum(string $body): string
{
    return md5($body);
}

/**
 * Расчитываем подпись запроса
 *
 * @param string $secret Секретный ключ вашего канала
 * @param string $checkSum Рассчитанный хэш тела запроса
 * @param string $apiMethod Адрес вызываемого метода API
 * @param string $httpMethod HTTP метод запроса
 * @param string $contentType Передаваемый тип данных
 *
 * @return string
 */
function createSignature(
    string $secret,
    string $checkSum,
    string $apiMethod,
    string $httpMethod = 'POST',
    string $contentType = 'application/json'
): string {
    $str = implode("\n", [
        strtoupper($httpMethod),
        $checkSum,
        $contentType,
        date(DateTimeInterface::RFC2822),
        $apiMethod,
    ]);

    return hash_hmac('sha1', $str, $secret);
}

/**
 * Подготавливаем заголовки для запроса
 *
 * @param string $checkSum Рассчитанный хэш тела запроса
 * @param string $signature Рассчитанная подпись запроса
 * @param string $contentType Передаваемый тип данных
 *
 * @return array
 */
function prepareHeaderForCurl(
    string $checkSum,
    string $signature,
    string $contentType = 'application/json'
): array {
    $headers = [
        'Date' => date(DateTimeInterface::RFC2822),
        'Content-Type' => $contentType,
        'Content-MD5' => strtolower($checkSum),
        'X-Signature' => strtolower($signature),
        'User-Agent' => 'amoCRM-Chats-Doc-Example/1.0'
    ];

    foreach ($headers as $name => $value) {
        $curlHeaders[] = $name . ": " . $value;
    }

    return $curlHeaders;
}

/**
 * Выполняем запрос к API Чатов
 *
 * @param string $apiMethod Запрашиваемый метод API
 * @param string $requestBody Тело запроса
 * @param array $requestHeaders Заголовки запроса
 * @param string $httpMethod HTTP метод запроса
 */
function execCurl(
    string $apiMethod,
    string $requestBody,
    array $requestHeaders,
    string $httpMethod = 'POST'
): void {
    $curl = curl_init();
    $curlOptions = [
        CURLOPT_URL => 'https://amojo.amocrm.ru' . $apiMethod,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 5,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => $httpMethod,
        CURLOPT_HTTPHEADER => $requestHeaders,
    ];

    if (!empty($requestBody)) {
        $curlOptions[CURLOPT_POSTFIELDS] = $requestBody;
    }

    curl_setopt_array($curl, $curlOptions);

    $response = curl_exec($curl);
    $error = curl_error($curl);
    $info = curl_getinfo($curl);
    curl_close($curl);
    if ($error) {
        echo "cURL Error #:" . $error;
    } else {
        echo "Status: " . $info['http_code'] . PHP_EOL;
        echo $response . PHP_EOL;
    }
}

Подключение канала к аккаунту

После создания канала, необходимо выполнить подключение канала к аккаунту.
Для этого необходимо воспользоваться методом подключения канала.
Важно отметить, если ваш канал еще не является публичным, то возможность подключения ограниченна перечисленными при регистрации аккаунтами.

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

Рассмотрим пример кода:

<?php

include __DIR__ . '/helpers.php';

// Секретный ключ канала
$channelSecret = 'f2d7f8704eff95087ed45b23ba99c0b5aac8278e';
// ID канала в сервисе чатов, который подключаем к каналу
$channelId = '344a5002-f8ca-454d-af3d-396180102ac7';
// Тело запроса
$requestBody = [
    'account_id' => '52e591f7-c98f-4255-8495-827210138c81',
    'title' => 'ChatsIntegration',
    'hook_api_version' => 'v2',
];
$jsonBody = json_encode($requestBody);
$checkSum = createBodyChecksum($jsonBody);
$apiMethod = sprintf('/v2/origin/custom/%s/connect', $channelId);

// Составим подпись
$signature = createSignature(
    $channelSecret,
    $checkSum,
    $apiMethod
);

// Подготовим заголовки
$curlHeaders = prepareHeaderForCurl($checkSum, $signature);

echo 'POST ' . $apiMethod . PHP_EOL;
foreach ($curlHeaders as $header) {
    echo $header . PHP_EOL;
}
echo PHP_EOL . $jsonBody . PHP_EOL . PHP_EOL;

// Выполним запрос
execCurl($apiMethod, $jsonBody, $curlHeaders);

Запрос

POST https://amojo.amocrm.ru/v2/origin/custom/344a5002-f8ca-454d-af3d-396180102ac7/connect
Date: Wed, 15 Dec 2021 23:12:55 +0000
Content-Type: application/json
Content-MD5: 2fbcb3c652b61e24e004daddfc73d0ce
X-Signature: 81211856ce0c5095f3e1a90c0a38dc9d736cfd55
User-Agent: amoCRM-Chats-Doc-Example/1.0

Тело запроса

{
  "account_id": "52e591f7-c98f-4255-8495-827210138c81",
  "title": "ChatsIntegration",
  "hook_api_version": "v2"
}

Ответ

{
  "account_id": "52e591f7-c98f-4255-8495-827210138c81",
  "scope_id": "344a5002-f8ca-454d-af3d-396180102ac7_52e591f7-c98f-4255-8495-827210138c81",
  "title": "ChatsIntegration",
  "hook_api_version": "v2"
}

Отправка сообщения из чата в amoCRM

После подключения канала к аккаунту, вы получите возможность пользоваться методом API.
Один из первых методов, с которым вы столкнетесь – отправка сообщения из чата в amoCRM.

Для отправки необходимо воспользоваться методом добавления сообщений.

Данный метод позволяет как добавлять входящие сообщение, так и исходящие, в данном разделе рассмотрим кейс входящего сообщения из стороннего чата в amoCRM.

Также стоит отметить, что API чатов не принимает несколько сообщений с одинаковым payload[msgid].
А также при передаче payload[conversation_id], которого ранее не было в системе – будет создан новый чат.

Рассмотрим пример кода:

<?php

include __DIR__ . '/helpers.php';

// Секретный ключ канала
$channelSecret = 'f2d7f8704eff95087ed45b23ba99c0b5aac8278e';
// Scope ID, который был получен при подключении канала в аккаунт
$scopeId = '344a5002-f8ca-454d-af3d-396180102ac7_52e591f7-c98f-4255-8495-827210138c81';
// Тело запроса, отправим сообщение в amoCRM, которое написал нам клиент
$requestBody = [
    'event_type' => 'new_message',
    'payload' => [
        'timestamp' => time(),
        'msec_timestamp' => round(microtime(true) * 1000),
        'msgid' => 'my_int-5f2836a8ca481',
        'conversation_id' => 'my_int-d5a421f7f218',
        'sender' => [
            'id' => 'my_int-1376265f-86df-4c49-a0c3-a4816df41af8',
            'avatar' => 'https://images.pexels.com/photos/10050979/pexels-photo-10050979.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500',
            'profile' => [
                'phone' => '+79151112233',
                'email' => 'example.client@example.com',
            ],
            'profile_link' => 'https://example.com/profile/example.client',
            'name' => 'Вася клиент',
        ],
        'message' => [
            'type' => 'text',
            'text' => 'Сообщение от клиента',
        ],
        'silent' => false,
    ],
];
$jsonBody = json_encode($requestBody);
$checkSum = createBodyChecksum($jsonBody);
$apiMethod = sprintf('/v2/origin/custom/%s', $scopeId);

// Составим подпись
$signature = createSignature(
    $channelSecret,
    $checkSum,
    $apiMethod
);

// Подготовим заголовки
$curlHeaders = prepareHeaderForCurl($checkSum, $signature);

echo 'POST ' . $apiMethod . PHP_EOL;
foreach ($curlHeaders as $header) {
    echo $header . PHP_EOL;
}
echo PHP_EOL . $jsonBody . PHP_EOL . PHP_EOL;

// Выполним запрос
execCurl($apiMethod, $jsonBody, $curlHeaders);

Запрос

POST https://amojo.amocrm.ru/v2/origin/custom/344a5002-f8ca-454d-af3d-396180102ac7_52e591f7-c98f-4255-8495-827210138c81
Date: Thu, 16 Dec 2021 13:15:29 +0000
Content-Type: application/json
Content-MD5: 223ef85def871bc7cb58aa6f02f0a26e
X-Signature: 29b49e9eaf03f13a66ecaff315f2855b31b25eee
User-Agent: amoCRM-Chats-Doc-Example/1.0

Тело запроса

Разберем некоторые из параметров запроса, которые могут вызывать вопросы:

  • payload[msgid] – в данном поле передаем ID сообщения на стороне вашей интеграции
  • payload[conversation_id] – ID чата на стороне вашей интеграции
  • payload[sender][id] – ID отправителя сообщения на стороне вашей интеграции
  • payload[sender][avatar] – ссылка на аватар пользователя, спустя небольшое время после передачи сообщения, мы сделаем GET запрос на указанный адрес, чтобы скачать изображение
  • payload[sender][profile] – объект с телефоном и email пользователя, поле опционально для передачи в запросе. При создании нового контакта, будут использованы для контроля дублей и зафиксируются в созданной карточке контакта.
  • payload[sender][profile_link] – ссылка на профиль клиента, в данный момент нигде не выводится

Для передачи сообщений с медиа вложениями, необходимо в message[type] указать необходимый тип и передать структуру в соответствии со спецификацией метода.

{
  "event_type": "new_message",
  "payload": {
    "timestamp": 1639660529,
    "msec_timestamp": 1639660529379,
    "msgid": "my_int-5f2836a8ca481",
    "conversation_id": "my_int-d5a421f7f218",
    "sender": {
      "id": "my_int-1376265f-86df-4c49-a0c3-a4816df41af8",
      "avatar": "https://images.pexels.com/photos/10050979/pexels-photo-10050979.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500",
      "profile": {
        "phone": "+79151112233",
        "email": "example.client@example.com"
      },
      "profile_link": "https://example.com/profile/example.client",
      "name": "Вася клиент"
    },
    "message": {
      "type": "text",
      "text": "Сообщение от клиента"
    },
    "silent": false
  }
}

Ответ

В ответе вы получите ID сообщения на стороне amoCRM (new_message[msgid]).

{
  "new_message": {
    "msgid": "8e6afe4e-a08a-4801-b6cb-37963c1a6f3c",
    "ref_id": "my_int-5f2836a8ca481"
  }
}

Получение сообщения, отправленного из amoCRM, разбор вебхука

Когда пользователь отправляет сообщение из amoCRM в ваш канал, мы отправляем на указанный адрес webhook.
После получения вебхука, вам необходимо обработать его и отправить сообщение в мессенджер.
Если вы получили сообщение, которое интегрированный мессенджер не может принять, вам необходимо обновить статус сообщения на ошибочный.

Также мы настойчиво рекомендуем обрабатывать сообщения и отправлять в мессенджер вне контекста запроса.
То есть, при получении вебхука, вы проверяете подпись запроса, а дальше, например, ставите задачу на обработку в очередь и отдаете ответ на вебхук.
Дальше, уже в асинхронном режиме, вы обрабатываете сообщение, производите вашу бизнес-логику и отправляете сообщение.

В данный момент мы ждем ответа на хук не больше 5 секунд. Для того чтобы мы посчитали хук успешно отправленным,
ваша интеграция должна ответить на запрос с http-кодом 200.

API Чатов имеет механизмы приоритизации, если интеграция начинает долго отвечать на хуки или не отвечать вовсе,
мы можем вынести отдельно от основного потока сообщений в медленный поток, в котором хуки могут приходить с задержкой.
Приоритет рассчитывается динамически и зависят от разных факторов, интеграция может перейти как в более быструю очередь, так и в более медленную.

Описание формата Webhook’ов от API Чатов (Webhook Reference)

Рассмотрим пример кода, который обрабатывает структуру полученного хука:

<?php

include __DIR__ . '/helpers.php';

// Определим методы, которые выполняют бизнес-логику
function saveTextMessage(
    string $messageReceiver,
    string $chatId,
    string $text
) {
    // Сохраняем текстовое сообщение
}

function savePictureMessage(
    string $messageReceiver,
    string $chatId,
    array $file,
    string $messageText
) {
    // Сохраняем изображение
}

function saveFileMessage(
    string $messageReceiver,
    string $chatId,
    array $file,
    string $messageText
) {
    // Сохраняем файл
}

function downloadFile(string $url): array
{
    // сохраняем файл по ссылке на диск, возвращаем информацию о файле
    return [];
}

function setErrorDeliveryStatus($messageId) {
    //Обновляем статус доставки сообщения с информацией, что оно не обработано
}

// Секретный ключ канала
$channelSecret = 'f2d7f8704eff95087ed45b23ba99c0b5aac8278e';

if ($_SERVER ['REQUEST_METHOD'] !== 'POST') {
    throw new RuntimeException('Unsupported request');
}

//Полученное тело запроса
$str = file_get_contents('php://input');
$body = stream_get_contents(STDIN);
if (empty($body)) {
    throw new RuntimeException('Empty body');
}
$signature = hash_hmac('sha1', $str, $channelSecret);

// Проверяем подпись
if (!isset($_SERVER['HTTP_X_SIGNATURE']) || $signature !== $_SERVER['HTTP_X_SIGNATURE']) {
    echo 'Invalid hook';
    die;
}

$hookBody = json_decode($body, true);
if (!$hookBody) {
    throw new RuntimeException('Unsupported body');
}

// ID аккаунта в API чатов
$accountId = $hookBody['account_id'];

// ID сообщения в API чатов
$messageId = $hookBody['message']['message']['id'];
// Тип сообщения
$messageType = $hookBody['message']['message']['type'];
// Тип сообщения
$messageText = $hookBody['message']['message']['text'];
// ID чата, в которое было отправлено сообщение на стороне интеграции
$messageChatId = $hookBody['message']['conversation']['client_id'];
// ID чата, в которое было отправлено сообщение на стороне amoCRM
$messageAmoCrmChatId = $hookBody['message']['conversation']['id'];
// Ссылка на прикрепленный к сообщению файл
$fileLink = $hookBody['message']['message']['media'] ?? null;
// ID получателя на стороне интеграции
$receiverId = $hookBody['message']['receiver']['id'];

switch ($hookBody['message']['message']['type']) {
    case 'text':
        saveTextMessage(
            $receiverId,
            $messageChatId,
            $messageText
        );
        break;
    case 'picture':
        $downloadedFile = downloadFile($fileLink);
        savePictureMessage(
            $receiverId,
            $messageChatId,
            $downloadedFile,
            $messageText
        );
        break;
    case 'file':
        $downloadedFile = downloadFile($fileLink);
        saveFileMessage(
            $receiverId,
            $messageChatId,
            $downloadedFile,
            $messageText
        );
        break;
    default:
        setErrorDeliveryStatus($messageId);
        throw new RuntimeException('Unsupported message type');
        break;
}

Тело запроса

{
  "account_id": "52e591f7-c98f-4255-8495-827210138c81",
  "time": 1639572261,
  "message": {
    "receiver": {
      "id": "2ed64e26-70a1-4857-8382-bb066a076219",
      "phone": "79161234567",
      "email": "example.client@example.com",
      "client_id":"my_int-1376265f-86df-4c49-a0c3-a4816df41af8"
    },
    "sender": {
      "id": "76fc2bea-902f-425c-9a3d-dcdac4766090"
    },
    "conversation": {
      "id": "8e4d4baa-9e6c-4a88-838a-5f62be227bdc",
      "client_id":"my_int-d5a421f7f218"
    },
    "source":{
      "external_id":"78001234567"
    },
    "timestamp": 1639572260,
    "msec_timestamp": 1639572260980,
    "message": {
      "id": "0371a0ff-b78a-4c7b-8538-a7d547e10692",
      "type": "picture",
      "text": "Текст сообщения Сделка #15926745",
      "markup": {
        "mode": "inline",
        "buttons": [
          [
            {
              "text":"Принять заказ"
            },
            {
              "text":"Отменить заказ"
            }
          ]
        ]
      },
      "tag": "",
      "media": "https://amojo.amocrm.ru/attachments/image.jpg",
      "thumbnail": "https://amojo.amocrm.ru/attachments/image_320x200.jpg",
      "file_name": "",
      "file_size": 0,
      "template": {
        "id": 7103,
        "content": "Текст сообщения {{lead.name}}",
        "params": [
          {
            "key": "{{lead.id}}",
            "value": "15926745"
          }
        ]
      }
    }
  }
}

Обновление статуса отправки сообщения

После получения сообщения, интеграция передает его в мессенеджер.
Статус сообщения может измениться: сообщение может быть не доставлено или может стать прочитанным.

Через API чатов можно обновить статус сообщения, а также передать информацию об ошибке.
Для этого необходимо воспользоваться методом обновления статуса сообщения.

Рассмотрим пример кода, в котором мы обновим сообщение 079e44fb-fc22-476b-9e8a-421b688ec53b и укажем статус "Не доставлено":

<?php

include __DIR__ . '/helpers.php';

// Секретный ключ канала
$channelSecret = 'f2d7f8704eff95087ed45b23ba99c0b5aac8278e';
// Scope ID, который был получен при подключении канала в аккаунт
$scopeId = '344a5002-f8ca-454d-af3d-396180102ac7_52e591f7-c98f-4255-8495-827210138c81';
// ID сообщения, которое было получено в хуке
$messageId = '079e44fb-fc22-476b-9e8a-421b688ec53b';
// Тело запроса, обновим сообщение с информацией об ошибке
$requestBody = [
    'msgid' => '079e44fb-fc22-476b-9e8a-421b688ec53b',
    'delivery_status' => -1,
    'error_code' => 905,
    'error' => 'Error text'
];
$jsonBody = json_encode($requestBody);
$checkSum = createBodyChecksum($jsonBody);
$apiMethod = sprintf('/v2/origin/custom/%s/%s/delivery_status', $scopeId, $messageId);

// Составим подпись
$signature = createSignature(
    $channelSecret,
    $checkSum,
    $apiMethod
);

// Подготовим заголовки
$curlHeaders = prepareHeaderForCurl($checkSum, $signature);

echo 'POST ' . $apiMethod . PHP_EOL;
foreach ($curlHeaders as $header) {
    echo $header . PHP_EOL;
}
echo PHP_EOL . $jsonBody . PHP_EOL . PHP_EOL;

// Выполним запрос
execCurl($apiMethod, $jsonBody, $curlHeaders);

Запрос

POST https://amojo.amocrm.ru/v2/origin/custom/344a5002-f8ca-454d-af3d-396180102ac7_52e591f7-c98f-4255-8495-827210138c81/079e44fb-fc22-476b-9e8a-421b688ec53b/delivery_status
Date: Thu, 16 Dec 2021 12:58:40 +0000
Content-Type: application/json
Content-MD5: a0bbbac729c6341f0a521e2db7a8a236
X-Signature: ca51f810785031385a801f41103244713c1a2352
User-Agent: amoCRM-Chats-Doc-Example/1.0

Тело запроса

{
  "msgid": "079e44fb-fc22-476b-9e8a-421b688ec53b",
  "delivery_status": 2,
  "error_code": 905,
  "error": "Error text"
}

Ответ

Метод не возвращает ответа

Отключение канала от аккаунта

В случае отключения интеграции, необходимо выполнить отключение канала от аккаунта.
Для этого необходимо воспользоваться методом отключение канала.

Рассмотрим пример кода:

<?php

include __DIR__ . '/helpers.php';

// Секретный ключ канала
$channelSecret = 'f2d7f8704eff95087ed45b23ba99c0b5aac8278e';
// ID канала в сервисе чатов, который подключаем к каналу
$channelId = '344a5002-f8ca-454d-af3d-396180102ac7';
// Тело запроса
$requestBody = [
    'account_id' => '52e591f7-c98f-4255-8495-827210138c81',
];
$jsonBody = json_encode($requestBody);
$checkSum = createBodyChecksum($jsonBody);
$apiMethod = sprintf('/v2/origin/custom/%s/disconnect', $channelId);

// Составим подпись
$signature = createSignature(
    $channelSecret,
    $checkSum,
    $apiMethod,
    'DELETE'
);

// Подготовим заголовки
$curlHeaders = prepareHeaderForCurl($checkSum, $signature);

echo 'DELETE ' . $apiMethod . PHP_EOL;
foreach ($curlHeaders as $header) {
    echo $header . PHP_EOL;
}
echo PHP_EOL . $jsonBody . PHP_EOL . PHP_EOL;

// Выполним запрос
execCurl($apiMethod, $jsonBody, $curlHeaders, 'DELETE');

Запрос

DELETE https://amojo.amocrm.ru/v2/origin/custom/344a5002-f8ca-454d-af3d-396180102ac7/disconnect
Date: Wed, 15 Dec 2021 23:59:28 +0000
Content-Type: application/json
Content-MD5: 271fa2e4fb0ff84a3ef9689027bd1f38
X-Signature: e1678e3b6aa674b5127f4feb448e6200ce0bfc72
User-Agent: amoCRM-Chats-Doc-Example/1.0

Тело запроса

{
  "account_id": "52e591f7-c98f-4255-8495-827210138c81"
}

Ответ

Метод не возвращает тело ответа

Разработка тиражируемого решения

Вы можете предоставить другим пользователям amoCRM возможность подключить ваш канал к своему аккаунту.
Для этого необходимо разработать виджет, с помощью которого пользователь сможет связать свой аккаунт с вашим каналом.

Подробнее о разработке виджета вы можете прочитать в документации по виджетам.

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

В manifest.json, в объект locations вам требуется добавить параметр “lead_sources”.
После этого ваш виджет отобразится в разделе подключения источников.

Для отображения в модальном окне подключения интеграций с WhatsApp, необходимо также добавить в объект locations параметр “whatsapp_modal”.

Модальное окно подключения источников

После открытия окна подключения источников, пользователь открывает виджет и устанавливает его.
Затем производит настройку, например вводит логин и пароль,
JS часть виджета отправляет на ваш сервер связку логин/пароль и ID аккаунта.
ID аккаунта может быть получен виджетом через следующий JS скрипт: APP.constant("account").amojo_id
Ваш сервер проверяет учетные данные и вызывает запрос на подключение нового аккаунта к каналу чатов.
После установки виджета в Digital Pipeline будет создан источник.

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

Если в интеграции устновлена галка "Множественные источники", источник по-умолчанию создан не будет.
Если интеграция указывает эту галку, мы считаем, что интеграция сама управляет своими источниками.

Пример виджета

Скачать пример виджета

Поддержка и создание множественных источников

По-умолчанию, мы считаем, что канал представляет собой подключение одного типа чата (WhatsApp, Viber, Facebook, Онлайн-чат на cайт и другие)
и позволяет возможность передавать сообщения между amoCRM и конечной чат-платформой через интеграцию.

Так как бизнес имеет необходимость разделять общение со своими клиентами по нескольким направлениям
(это может быть несколько телефонных номеров WhatsApp для разных отделов или разные страницы лендингов с онлайн-чатами),
то и канал общения тоже требуется разделять. Для разделения в рамках одного типа чатов используются источники.

Таким образом с помощью источника можно определить, что клиент обратился к бизнесу с определенного лендинга или написал сообщение на телефонный номер конкретного отдела компании.
И в дальнейшем уже выстроить работу с каждым источником индивидуально: распределение по воронкам и работа ботов в зависимости от источника.

В большинстве случаев, интеграция предоставляет связь между amoCRM и одной конкретной чат-платформой.
Таким образом интеграции необходим всего лишь один канал для обеспечения передачи сообщений между аккаунтом amoCRM и чат-платформой.

Если источник требуется только 1, то amoCRM создает источник при установке интеграции.
Если же интеграции требуется несколько источников, это следует учесть при разработке интеграции.

Для реализации поддержки множественных источников интеграции необходимо создать источники через API.
При создании источника, в external_id необходимо указать идентификатор источника на стороне интеграции.
Заданный external_id вы сможете передавать при создании чата или добавлении сообщений, чтобы они были явно связанны с конкретным источником.
Также external_id будет приходить в хуках сообщений, которые были написаны в конкретный канал.

Для того чтобы интеграция поддерживала множественные источники, необходимо, чтобы у интеграции был загружен архив, а в locations в manifest.json присутствовало значение "lead_sources".

Если интеграции не требуется автоматическое создание источника, то в настройках интеграции необходимо установить гавлку "Множественные источники".

Источник по-умолчанию

Источник "по-умолчанию" позволяет интеграции указать amoCRM, как работать с чатами, у которых не указан источник или передан незарегистрированный источник.
Т.е. он позволяет массово разметить уже существующие чаты при внедрении интеграцией множественных источников.

Источников "по-умолчанию" (с полем default=true) может быть не более одного на аккаунт для одной интеграции.

Если интеграция не поддерживает работу с множественными источниками, то у нее будет всего 1 источник, который создает amoCRM при добавлении интеграции в качестве источника в Digital Pipeline или установке интеграции.
У таких источников в поле external_id прописан код виджета. Интеграция может его удалить или назначить другой источник "по-молчанию" через API.

В случае, когда интеграция поддерживает работу с множественными источниками, то amoCRM не будет создавать источник "по-умолчанию" самостоятельно при добавлении интеграции администратором в качестве источника.
И все необходимые источники, интеграция создает уже самостоятельно.
Для этого надо указать соответствующий флаг в настройках интеграции перед передачей на модерацию виджета интеграции.

Что бы сменить источник по-умолчанию, необходимо через API проставить поле default=true у источника. У предыдущего источника (если он был) поле default будет выставлено в default=false.

Так как источник "по-умолчанию" не обязателен, то при его удалении ни один другой источник не станет "по-умолчанию" автоматически. Интеграция должна указать новый источник по-умолчанию самостоятельно.

Важный момент
Источник по-умолчанию не привязывается к чатам, если его явно не передавали с сообщениями.

К примеру:
Есть сделка с контактом и чатом, чат не привязан к источнику.

И существуют 2 источника:

  • Отдел технической поддержки
  • Отдел продаж

Мы назначаем "Отдел технической поддержки" источником по-умолчанию.
Теперь при отправке пользователем сообщения в чат интеграции придет источник "Отдел технической поддержки"

Если назначить источником по-умолчанию "Отдел продаж", то в последующих отправленных из amoCRM сообщениях, external_id в хуке будет равен "Отдел продаж"

Такое поведение будет сохраняться до момента передачи интеграцией источника вместе с сообщением.
Как только интеграция сообщит, что чат относится к конкретному источнику логика источника по-умолчанию перестанет действовать для чата.
Вернуть размеченный чат обратно к логике по-умолчанию уже нельзя.

Особенности работы интеграции с несколькими каналами чатов

Данные предостережения стоит рассматривать только в случае, если за интеграцией закреплено более 1 канала и интеграция использует возможность "Написать первым"

Каждая интеграция может управлять только своими источниками, и среди источников интеграции может быть только один источник "по-умолчанию".

Данный момент накладывает дополнительные ограничения на интеграцию при управлении источниками:

  • интеграции требуется самостоятельно разрешать конфликты идентификаторов источников на своей стороне
  • источник по-умолчанию может использоваться только с одним каналом. Нужно дополнительно прорабатывать процесс миграции на множественные источники
  • для каждого чата/сообщения необходимо явно надо передавать источник, что бы все чаты имели явную привязку к источнику
  • если будет передан неизвестный для amoCRM источник, то в качестве источника будет указана сама интеграция (тот же источник, что используется при создании сделки через API)

В данной ситуации, одним из решений может быть предоставление выбора источника "по-умолчанию" пользователю в настройках интеграции.
Что бы пользователь понимал, что для чатов без явного указания источника будет использоваться конкретный канал и выбранный источник.

Логика выбора источника при создании чата

Если интеграция не создавала источников самостоятельно,
то при создании неразобранной сделки из чата, в сделке будет указан созданный нами (при подключении интеграции) источник с названием интеграции.
Если интеграция передает в сообщениях несуществующий или удаленный источник, тогда мы проверим, есть ли источник по-умолчанию (свойство источника default=true).
Если источника по-умолчанию нет – тогда в сделках будет указан источник с названием интеграции (как при создании неразобранного через API неразобранного).

Указание источника при работе с API чатов

Интеграция может указать в источнике воронку, где будет создано неразобранное при первом входящем сообщении.
Для этого необходимо чтобы внешний идентификатор источника source[external_id],
передаваемый в сообщениях
(или при создании чата) совпадал с external_id существующего источника.
В случае когда пользователь начинает переписку с клиентом первым и при этом пользователь выбрал созданный интеграцией источник, то выбранный источник передается интеграции в хуке.
Таким образом интеграция может понять в рамках какого источника была начата переписка, и, к примеру, для WhatsApp интеграций,
отправить клиенту сообщения с конкретного подключенного номера телефона, если администратор подключил несколько номеров.

Внедрение множественных источников в действующие интеграции

Источник по-умолчанию может быть использован интеграцией при переходе на множественные источники, особенно если
интеграция поддерживает функционал "Написать первым".

К примеру, имеем исходное состояние:
Есть аккаунт с подключенной интеграцией с чатами, и эта интеграция поддерживает только 1 источник.
На данный момент нам не важно как была установлена интеграция: через Digital Pipeline или маркетплейс.

Интеграция начинает внедрение поддержки множественных источников, этот процесс логически разобьем на этапы:

1 этап
Интеграция умеет работать с API источниками (но не отправляет и не принимает источник в сообщениях amoJo).
На данном этапе интеграция добавляет источник по-умолчанию в аккаунт.
Данный источник по-умолчанию соответствует источнику, который использовался, когда не было поддержки нескольких источников.
Теперь в хуках о сообщениях будет приходить external_id этого источника для всех чатов, которые явно не закреплены за конкретным источником.

2 этап
Интеграция уже умеет работать с источниками и при отправке сообщений от клиента (при создании чата) указывает external_id.
Все чаты с новыми сообщениями становятся размеченными по переданному в сообщении источнику.

Так же интеграция теперь обрабатывает источник и учитывает его при отправке сообщения от менеджера, в том числе при начале чата с клиентом (функционал "Написать первым").

3 этап
Интеграция позволяет администратору аккаунта добавить (через интеграцию) второй и последующие источники.
Вся переписка по чатам закрепляется за каким-то явно указанным интеграцией источником, все неразмеченные чаты все еще числятся за источником по-умолчанию.

Пример цепочки запросов

После установки интеграции и получения Access Token, интеграция сможет создавать необходимые источники через API.
Рассмотрим цепочку вызовов:

  • Создание источника
  • Отправка сообщения с передачей информации об источнике
  • Вебхук с информацией об источнике

Представим, что мы разрабатываем интеграцию с WhatsApp с поддержкой нескольких номеров.
Для начала, создадим пару источников для отдела Продаж (+79039876543) и Технической поддержки (+79001234567).
Для идентификации источников возьмем номера телефонов которые, поддерживает наша интеграция: 79039876543 и 79001234567 соответственно.
Телефон технической поддержки будем считать, что пользователь выбрал как источник по умолчанию.
Также добавим номер отдела продаж – как возможный номер для кнопки на сайт.

Для этого отправим запрос на POST /api/v4/sources

Запрос
POST /api/v4/sources HTTP/1.1
Host: https://example.amocrm.ru
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbG....
Тело запроса
[
  {
    "name": "Support",
    "external_id": "79001234567",
    "services": [],
    "default": true
  },
  {
    "name": "Sales",
    "external_id": "79039876543",
    "services": [
      {
        "type": "whatsapp",
        "pages": [
          {
            "id": "79039876543",
            "name": "whatsapp",
            "link": "+79039876543"
          }
        ]
      }
    ],
    "default": false
  }
]
Ответ
{
  "_total_items": 2,
  "_embedded": {
    "sources": [
      {
        "id": 3108069,
        "name": "Support",
        "pipeline_id": 20453,
        "external_id": "79001234567",
        "services": [],
        "default": true,
        "request_id": "0",
        "_links": {
          "self": {
            "href": "https://example.amocrm.ru/api/v4/sources/3108069"
          }
        }
      },
      {
        "id": 3108070,
        "name": "Sales",
        "pipeline_id": 20453,
        "external_id": "79039876543",
        "services": [
          {
            "type": "whatsapp",
            "pages": [
              {
                "id": "79039876543",
                "name": "whatsapp",
                "link": "+79039876543"
              }
            ]
          }
        ],
        "default": false,
        "request_id": "1",
        "_links": {
          "self": {
            "href": "https://example.amocrm.ru/api/v4/sources/3108070"
          }
        }
      }
    ]
  }
}

Интеграции необходимо хранить связь external_id и например, номера телефона, на своей стороне.

Теперь мы можем отправлять сообщение в API Чатов с указанием источника, и неразобранное будет создано в воронке, в которой подключен переданный источник.

В данном случае клиент отправил сообщение на номер Отдела продаж и интеграция указала external_id источника в поле source[external_id] при отправке сообщения.

Запрос
POST https://amojo.amocrm.ru/v2/origin/custom/344a5002-f8ca-454d-af3d-396180102ac7_52e591f7-c98f-4255-8495-827210138c81
Date: Fri, 17 Dec 2021 10:59:28 +0000
Content-Type: application/json
Content-MD5: 353178c993f09a8ec5f3eab4093b753e
X-Signature: e895539b52051e1a8d3f89d454c7ad7406705234
User-Agent: amoCRM-Chats-Doc-Example/1.0
Тело запроса
{
  "event_type": "new_message",
  "payload": {
    "timestamp": 1639738768,
    "msec_timestamp": 1639738768457,
    "msgid": "my_int-5f2836a8ca481",
    "conversation_id": "my_int-d5a421f7f218",
    "sender": {
      "id": "my_int-1376265f-86df-4c49-a0c3-a4816df41af8",
      "avatar": "https://images.pexels.com/photos/10050979/pexels-photo-10050979.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500",
      "profile": {
        "phone": "+79151112233",
        "email": "example.client@example.com"
      },
      "profile_link": "https://example.com/profile/example.client",
      "name": "Вася клиент"
    },
    "message": {
      "type": "text",
      "text": "Сообщение от клиента"
    },
    "source": {
      "external_id": "79039876543"
    },
    "silent": false
  }
}
Ответ
{
  "new_message": {
    "msgid": "7779c89e-bb5b-4da0-b802-5de2ab7d4114",
    "ref_id": "my_int-5f2836a8ca481"
  }
}

После сообщения менеджера клиенту интеграция получит хук, также с указанием источника в поле source[external_id]

По источнику, интеграция должна найти нужный ей мессенджер на своей стороне и отправить ответ клиенту в нужный мессенджер, например, с номера отдела продаж.

Тело запроса

{
  "account_id": "52e591f7-c98f-4255-8495-827210138c81",
  "time": 1639572261,
  "message": {
    "receiver": {
      "id": "2ed64e26-70a1-4857-8382-bb066a076219",
      "phone": "+79151112233",
      "email": "example.client@example.com",
      "client_id":"my_int-1376265f-86df-4c49-a0c3-a4816df41af8"
    },
    "sender": {
      "id": "76fc2bea-902f-425c-9a3d-dcdac4766090"
    },
    "conversation": {
      "id": "8e4d4baa-9e6c-4a88-838a-5f62be227bdc",
      "client_id":"my_int-d5a421f7f218"
    },
    "source":{
      "external_id": "79039876543"
    },
    "timestamp": 1639572260,
    "msec_timestamp": 1639572260980,
    "message": {
      "id": "0371a0ff-b78a-4c7b-8538-a7d547e10692",
      "type": "text",
      "text": "Текст сообщения Сделка #15926745",
      "tag": "",
      "media": "",
      "thumbnail": "",
      "file_name": "",
      "file_size": 0
    }
  }
}

Связывание нового чата с существующим контактом

API Чатов вместе с API amoCRM позволяет создать новый контакт, чат и импортировать туда переписку.
API Чатов предоставляет отдельный метод создания чата, при вызове которого не будет создан новый контакт или сделка.
API amoCRM позволяет связать существующий чат с контактом по ID контакта.

С точки зрения бизнес логики, данный пример будет, когда клиент уже существующий и нам не нужно создание неразобранного.
Контакт в примере создадим для наглядности процесса.

Подробнее о методе создания чата можно прочитать в разделе API Reference.

Для начала создадим новый чат:

<?php

include __DIR__ . '/helpers.php';

// Секретный ключ канала
$channelSecret = 'f2d7f8704eff95087ed45b23ba99c0b5aac8278e';
// Scope ID, который был получен при подключении канала в аккаунт
$scopeId = '344a5002-f8ca-454d-af3d-396180102ac7_52e591f7-c98f-4255-8495-827210138c81';
// Тело запроса
$requestBody = [
    'conversation_id' => 'my_int-8e3e7640-49af-4448-a2c6-d5a421f7f217',
    'source' => [
        'external_id' => '78001234567', // external_id источника в API Источников, поле не передается, если интеграция не поддерживает множественные источники
    ],
    'user' => [
        'id' => 'my_int-1376265f-86df-4c49-a0c3-a4816df41af9',
        'avatar' => 'https://example.com/users/avatar.png',
        'name' => 'Имя клиента',
        'profile' => [
            'phone' => '+79151112233',
            'email' => 'example.client@example.com',
        ],
        'profile_link' => 'https://example.com/profile/example.client',
    ]
];
$jsonBody = json_encode($requestBody);
$checkSum = createBodyChecksum($jsonBody);
$apiMethod = sprintf('/v2/origin/custom/%s/chats', $scopeId);

// Составим подпись
$signature = createSignature(
    $channelSecret,
    $checkSum,
    $apiMethod
);

// Подготовим заголовки
$curlHeaders = prepareHeaderForCurl($checkSum, $signature);

echo 'POST ' . $apiMethod . PHP_EOL;
foreach ($curlHeaders as $header) {
    echo $header . PHP_EOL;
}
echo PHP_EOL . $jsonBody . PHP_EOL . PHP_EOL;

// Выполним запрос
execCurl($apiMethod, $jsonBody, $curlHeaders);

Запрос

POST https://amojo.amocrm.ru/v2/origin/custom/344a5002-f8ca-454d-af3d-396180102ac7_52e591f7-c98f-4255-8495-827210138c81/chats
Date: Thu, 16 Dec 2021 23:22:49 +0000
Content-Type: application/json
Content-MD5: 5139e573382c0eae38f476822abb2014
X-Signature: a601e486d694c8e7e8a5b637660f949f8bb7efaf
User-Agent: amoCRM-Chats-Doc-Example/1.0

Тело запроса

{
  "conversation_id": "my_int-8e3e7640-49af-4448-a2c6-d5a421f7f217",
  "source": {
    "external_id": "78001234567"
  },
  "user": {
    "id": "my_int-1376265f-86df-4c49-a0c3-a4816df41af9",
    "avatar": "https://example.com/users/avatar.png",
    "name": "Имя клиента",
    "profile": {
      "phone": "+79151112233",
      "email": "example.client@example.com"
    },
    "profile_link": "https://example.com/profile/example.client"
  }
}

Ответ

{
  "id": "31d43a78-e09d-46ae-8994-7e93560169b8",
  "user": {
    "id": "7c7330cd-c0ee-45a5-bd62-643a3a8225e8",
    "client_id": "my_int-1376265f-86df-4c49-a0c3-a4816df41af9",
    "name": "Имя клиента",
    "profile": {
      "phone": "79151112233",
      "email": "example.client@example.com"
    },
    "avatar": "https://example.com/users/avatar.png"
  }
}

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

Пример запроса

POST /api/v4/contacts HTTP/1.1
Host: https://{subdomain}.amocrm.ru
Content-Type: application/json
Authorization: Bearer xxxx

Тело запроса

[
  {
    "first_name": "Jack200803",
    "last_name": "Tester"
  }
]

Ответ

    {
        "_links": {
            "self": {
                "href": "https://{subdomain}.amocrm.ru/api/v4/contacts"
            }
        },
        "_embedded": {
            "contacts": [
                {
                    "id": 3102959,
                    "request_id": "0",
                    "_links": {
                        "self": {
                            "href": "https://{subdomain}.amocrm.ru/api/v4/contacts/3102959"
                        }
                    }
                }
            ]
        }
    }

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

Пример запроса

POST /api/v4/contacts/chats HTTP/1.1
Host: https://{subdomain}.amocrm.ru
Content-Type: application/json
Authorization: Bearer xxxx

Тело запроса

[
  {
    "contact_id": 3102959,
    "chat_id": "6cbab3d5-c4c1-46ff-b710-ad59ad10805f"
  }
]

Ответ

    {
        "_total_items": 1,
        "_embedded": {
            "chats": [
                {
                    "chat_id": "6cbab3d5-c4c1-46ff-b710-ad59ad10805f",
                    "contact_id": 3102959,
                    "id": 26219,
                    "request_id": "0"
                }
            ]
        }
    }

Также можно проверить привязку чата и контакта. Для этого можно воспользоваться методом API.

Импорт существующей переписки

Метод добавления сообщений
позволяет, кроме добавления новых сообщение от клиента, производить импорт существующих сообщений.

Импортируемые сообщения могут быть адресованы:

  • Входящее от клиента
  • Исходящее клиенту от пользователя amoCRM
  • Исходящее клиенту от бота интеграции

Метод позволяем импортировать сообщения в чат, не вызывая уведомлений для пользователей amoCRM и создания неразобранного.
Для этого необходимо передать в метод API поле payload[silent]: true.
При массовом импорте старых сообщений в чат, рекомендуем передавать payload[silent]: true со всеми сообщениями, кроме последнего.
В последнем сообщении (самом свежем) передаем поле payload[silent]: false,
таким образом на последнее сообщение будет создано неразобранное и придет только одно уведомление, тем самым мы не создадим клиенту лишних беспокойств.

Также стоит отметить, что история сообщений в карточке отображается только за 24 часа до даты создания сделки/покупателя.

Подробную спецификацию и примеры можно найти в API Reference.

Написать первым

Еще одна возможность, которую предоставляет API Чатов – функция Написать первым.
Для получения доступа к функционалу, при создании чата, в заявке необходимо указать, что вам требуется эта функция.
Для существующих каналов её также можно включить через поддержку.

После включения функции "Написать первым", пользователи получают возможность создавать новый чат на стороне amoCRM в карточке сделки или через Salesbot.

Уникальным идентификатором для функционала является номер телефона. При создании чата, amoCRM пришлет интеграции вебхук v2.

Отличительной особенностью данного хука является отсутствие поля message[conversation][client_id],
в котором в обычных хуках приходит ID чата на стороне интеграции.

Если ваша интеграция поддерживает множественные источники, в хуке вы получите поле message[source][external_id],
в котором будет находиться переданный external_id при создании источника.

Также стоит отметить, что при добавлении нового сообщения через метод отправки сообщений,
вам необходимо передать payload[conversation_id] c ID чата на стороне интеграции, а также
payload[conversation_ref_id] с ID чата на стороне API чатов. Передача этих двух параметров позволит связать чат на стороне amoCRM с идентификатором чата на стороне интеграции.
Для связывания пользователя, которому мы пишем, с ближайшим входящим сообщением необходимо передать
поле payload[sender][ref_id] с ID пользователя на стороне API Чатов и payload[sender][id] c ID пользователя на стороне интеграции.

Отображение статуса печатания в карточке сделки

Некоторые мессенджеры уведомляют интеграции о том, что пользователь сейчас печатает. Интеграция может уведомить об этом amoCRM.
В таком случае в карточке, в которой отображается чат, появится анимация печати от имени пользователя мессенджера (клиента).

Важно отметить, что в запросе передается ID чата на стороне интеграции (ID переданный при отправке сообщения в payload[conversation_id]).
Также передается ID пользователя на стороне интеграции, который печатает.

Рассмотрим пример кода:

<?php

include __DIR__ . '/helpers.php';

// Секретный ключ канала
$channelSecret = 'f2d7f8704eff95087ed45b23ba99c0b5aac8278e';
// Scope ID, который был получен при подключении канала в аккаунт
$scopeId = '344a5002-f8ca-454d-af3d-396180102ac7_52e591f7-c98f-4255-8495-827210138c81';
// Тело запроса, отправим информацию, что пользователь печатает
$requestBody = [
    'conversation_id' => 'my_int-d5a421f7f218',
    'sender' => [
        'id' => 'my_int-1376265f-86df-4c49-a0c3-a4816df41af8',
    ],
];
$jsonBody = json_encode($requestBody);
$checkSum = createBodyChecksum($jsonBody);
$apiMethod = sprintf('/v2/origin/custom/%s/typing', $scopeId);

// Составим подпись
$signature = createSignature(
    $channelSecret,
    $checkSum,
    $apiMethod
);

// Подготовим заголовки
$curlHeaders = prepareHeaderForCurl($checkSum, $signature);

echo 'POST ' . $apiMethod . PHP_EOL;
foreach ($curlHeaders as $header) {
    echo $header . PHP_EOL;
}
echo PHP_EOL . $jsonBody . PHP_EOL . PHP_EOL;

// Выполним запрос
execCurl($apiMethod, $jsonBody, $curlHeaders);

Запрос

POST https://amojo.amocrm.ru/v2/origin/custom/344a5002-f8ca-454d-af3d-396180102ac7_52e591f7-c98f-4255-8495-827210138c81/typing
Date: Thu, 16 Dec 2021 16:01:21 +0000
Content-Type: application/json
Content-MD5: 255a22566d68be207fa36804e78a523a
X-Signature: e8263b1daf2b20396c02db6c8c846f95cc0f9540
User-Agent: amoCRM-Chats-Doc-Example/1.0

Тело запроса

{
  "conversation_id": "my_int-d5a421f7f218",
  "sender": {
    "id": "my_int-1376265f-86df-4c49-a0c3-a4816df41af8"
  }
}

Хуки о печати пользователем amoCRM

Мы предоставляем дополнительную возможность для интеграций – получать вебхуки, когда пользователь amoCRM начинает печатать сообщение.
Для получения подобных хуков вам необходимо обратиться в техническую поддержку или указать это при регистрации нового канала.

Запрос придет на тот же адрес, который был указан как адрес для получения Webhooks.

Мы считаем, что после каждого нажатия на клавишу, пользователь печатает еще до 5 секунд, поэтому в хуке мы присылаем timestamp-метку, показывающую, когда пользователь перестал печатать.
Вебхук отправляется не чаще, чем раз в 5 секунд.

После получения подобного хука, вам необходимо передать эту информацию в мессенджер, с которым вы интегрируетесь,
чтобы в мессенджере отобразилась информация о том, что мы сейчас печатаем.

Описание формата Webhook’ов о печати (Webhook Reference)

Получение истории сообщений чата

С помощью API чатов вы можете получить историю сообщений конкретного чата.
Для получения истории вам потребуется scope_id и ID чата на стороне API Чатов,
который можно получить в хуке о входящем сообщении или при создании чата через API.

Подробный пример доступен в cпецификации метода получения истории.

Поддержка реакций

С помощью API чатов вы можете послать реакцию на сообщения или снять реакцию с сообщения спецификация метода посылки реакции.

Если интеграция сообщила список поддерживаемых реакций, то менеджеры так же смогут реагировать
на сообщения из карточки. При этом в интеграцию будет приходить хук Описание формата Webhook’ов о реакции (Webhook Reference).