Быстрое создание callback-сценариев с помощью Voximplant

  • Tutorial
Не удивительно, что во многих бизнес процессах так или иначе встречается такая задача как позвонить на один номер телефона и потом соединить его с другим номером. Чаще всего такой сервис называют callback или обратный вызов, иногда — заказ звонка. Многие встречали его на сайтах интернет-магазинов, но в большинстве случаев это не автоматизированный обратный звонок, а обычная заявка, которая падает менеджеру на email или появляется в CRM, после чего менеджер ручками набирает номер клиента и с ним беседует. Некоторые крупные компании реализуют автоматизированный callback и даже интегрируют его с очередями в контакт-центре. В данном посте мы рассмотрим как буквально за несколько минут создать callback-сценарий с помощью платформы VoxImplant, а также интегрировать это хозяйство с каким-нибудь существующим бэкендом для сохранения/получения данных по звонку, запасайтесь поп-корном и добро пожаловать под кат.
Для начала продублируем схему в более крупном размере, чтобы было проще объяснить, что, как и с чем взаимодействует при реализации нашего callback-сервиса.

Выглядит немного громоздко, но на деле все очень просто и удобно. Разработчик VoxImplant (чтобы получить аккаунт разработчика нужно зарегистрироваться на сайте, это абсолютно бесплатно) через HTTP API делает запрос к облаку VoxImplant после чего сервер сценариев запускает заранее написанный на JS сценарий, в котором описана логика совершения звонков, их соединения, а также, при желании, бизнес-логика. Перед тем как делать HTTP-запрос для запуска сценария, очевидно, нужно его написать внутри приложения и прикрепить к правилу, id этого правила потом нужно будет указывать в HTTP-запросе, чтобы было понятно какой сценарий должен быть запущен. Итак, по порядку:
  1. Создаем приложение VoxImplant.
  2. Создаем в нем сценарий.
  3. Создаем правило и привязываем к нему сценарий.
  4. Делаем HTTP-запрос для запуска сценария.

После совершения HTTP-запроса для запуска сценария, если все сделано правильно, в ответ придет media_session_access_url, который позволяет после запуска сценария продолжить удаленное управление его работой. Например, если нужно в какой-то определенный момент закончить выполнение сценария, то можно написать в сценарии следующее:

VoxEngine.addEventListener(AppEvents.HttpRequest, handleHttpRequest); 
function handleHttpRequest(e) {
  VoxEngine.terminate();
}

В таком случае, сделав запрос по media_session_access_url, вы прекратите выполнение сценария. Возвращаемся к реализации нашего колбэка на телефонные номера. Создаем приложение в соответствующем разделе панели управления, назовем его банально callback (в итоге полное название будет callback.имя_аккаунта.voximplant.com), и в разделе «Сценарии» создаем новый сценарий CallbackHabr следующего вида:

var call1, call2, data;
const callerId = "+1234567890"; // Арендованный или верифицированный номер телефона

VoxEngine.addEventListener(AppEvents.Started, handleScenarioStart);

function handleScenarioStart(e) {
  // в сценарий можно передать данные с помощью механизма customData,
  // параметр script_custom_data в HTTP-запросе StartScenarios
  // передадим в нем номера в виде строки номер1:номер2
  data = VoxEngine.customData();
  data = data.split(":");
  // начало выполнения сценария - звоним на номер 1
  call1 = VoxEngine.callPSTN(data[0], callerId);
  // обработчики событий
  call1.addEventListener(CallEvents.Connected, handleCall1Connected);
  call1.addEventListener(CallEvents.Failed, function(e) { VoxEngine.terminate(); });
  call1.addEventListener(CallEvents.Disconnected, function(e) { VoxEngine.terminate(); });
}

function handleCall1Connected(e) {
  // первый звонок соединен успешно, проигрываем сообщение
  call1.say("Здравствуйте, это звонок от сервиса Колбэк, ожидайте соединения", Language.RU_RUSSIAN_FEMALE);
  call1.addEventListener(CallEvents.PlaybackFinished, function(e1) {
     // после проигрывания сообщения пытаемся дозвониться до номера 2
    call2 = VoxEngine.callPSTN(data[1], callerId);
    // обработчики событий
    call2.addEventListener(CallEvents.Connected, handleCall2Connected);
    call2.addEventListener(CallEvents.Failed, function(e2) { 
        call1.say("К сожалению, соединение не может быть установлено", Language.RU_RUSSIAN_FEMALE);
        call1.addEventListener(CallEvents.PlaybackFinished, function(e3) { VoxEngine.terminate(); });      
    });
    call2.addEventListener(CallEvents.Disconnected, function(e2) { VoxEngine.terminate(); });
  });
}

function handleCall2Connected(e) {
  // соединяем два звонка - звук 
  VoxEngine.sendMediaBetween(call1, call2);
  // и сигнализацию
  VoxEngine.easyProcess(call1, call2);
}

Если коротко, то данный сценарий звонит на номера, которые мы передаем через специальный параметр запроса StartScenarios script_custom_data в виде строки номер1: номер2. Сначала дозваниваемся на номер1 и проигрываем сообщение с помощью TTS, после чего дозваниваемся на номер2 и соединяем два звонка друг с другом. В данном сценарии мы никак не отрабатываем некоторые случаи, например, если не дозвонились на номер1, то просто завершаем выполнение сценария. Если немного поколдовать, то можно оповещать внешний мир о том, что что-то пошло не так, меняем
call1.addEventListener(CallEvents.Failed, function(e) { VoxEngine.terminate(); });

на
call1.addEventListener(CallEvents.Failed, handleCall1Failed);

и описываем handleCall1Failed:
function handleCall1Failed(e) {
  // тут можно узнать причину 
  var code = e.code,
      reason = e.reason;
  // и отправить ее во внешний мир с помощью HTTP-запроса
  Net.httpRequest("http://somewebservice", function(e1) {
    // информация по реузльтатам запроса - e1.code, e1.text, e1.data, e1.headers
    // убиваем сессию
    VoxEngine.terminate();
  });
}

Это самый простой пример с GET-запросом. Для настройки и управления HTTP-запросом со стороны сценария доступен класс HttpRequestOptions.
Таким же образом с помощью Date можно замерять длительность отдельных звонков или сессии внутри сценария и отправлять данные во внешний мир, если по каким-то причинам лезть в историю звонков для получения этой информации не соответствует логике сервиса/приложения. Как вы уже, наверное, поняли, мы пытались сделать очень гибкую систему, чтобы механизм решения тех или иных задач разработчик мог выбирать сам.
После того как сценарий готов, нам нужно пойти в раздел «Роутинг», чтобы подцепить его к правилу и в итоге получить ID правила:


Ну в общем и все, теперь можно делать HTTP-запрос StartScenarios, для которого потребуется еще ряд параметров, свой api_key можно узнать в разделе Profile. Запрос в итоге будет выглядеть как-то так:

https://api.voximplant.com/platform_api/StartScenarios/?account_id=1&api_key=eec36d6c-a0eb-46b5-a006-1c2b65343bac&rule_id=55033&script_custom_data=number1%3Anumber2

В результате успеха в ответ придет что-то в духе:
{
	"result" : 1,
	"media_session_access_url" : "http:\/\/1.2.3.4:12092\/request?id=93e41c6e20811b9b.1380201554.0@1.2.3.4&token=36bc7ce95edc679e32d83bb6f3ad985f"
}

Через этот media_session_access_url можно будет дальше общаться с сессией при необходимости. Для отладки работы сценария всегда доступны логи, которые находятся в разделе «История звонков», а также наш убердебагер, доступный внутри каждого из сценариев (кнопка сверху). Для удобства мы загрузили сценарий из данного поста на GitHub, чтобы можно было быстро поэкспериментировать.

Callback-сценарий на GitHub

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

Был ли данный материал Вам полезен?

  • 85,7%Да48
  • 14,3%Нет8
Voximplant
Облачная платформа голосовой и видеотелефонии

Комментарии 41

    +1
    ID правила можно увидеть только при выборе английского языка. В русском интерфейсе ID правила отсутствует
      0
      Спасибо за замечание, сейчас создадим тикет и постараемся поправить в самое ближайшее время.
        0
        Пробую… но пока некоторые вещи не очень понятны в документации. Например, где взять API Key?
          0
          В посте есть про это отдельно
          свой api_key можно узнать в разделе Profile
            +1
            Да, понял. Я уже прочитал :) Только это в вашем посте есть. А в документации — нету совсем.

            Ну и account_id — тоже не очень понятно где брать. Я плюс-минус разобрался, но вот документацию бы чуть-чуть дополнить.

            Вообще хорошо, что вы пишете такие статьи. Потому что примеров у вас на сайте практически нет — писать неимоверно сложно. Мне очень все нравится. Я готов запустить и начать пользоваться реальным серивисом вот прямо сейчас. Осталось только написать :)
              0
              account_id — это один из вариантов, вместо него можно указать account_name или email-адрес. Мы знаем, что наша дкоументация далека от совершенства на данный момент, мы работаем над ее улучшением и продолжим писать статьи как в блоге, так и на Хабре. Комментарии очень помогают сделать платформу лучше.
                0
                Дак мы с удовольствием бы! Только вот дальше куда смотреть? Вот я взял этот сценарий… а что мне дальше делать? Потому что пока не очень ясно…
                  0
                  После HTTP-запроса StartScenarios в облаке запустится данный сценарий и сделает звонок сначала на 1 номер, а потом на другой и соединит их.
                    0
                    Сорри, оно заработало. Оказывается, я неправильно вводил номер телефона. Правильный формат: 74951234567

                    Та-а-а-а-ак… а еще ма-а-а-аленький вопрос. Мне пришел звонок сейчас на мобильный. С некоего номера в коде 499. А что это за номер?
                      0
                      Это номер платформы в России по умолчанию, чтобы был другой номер надо поменять сценарий, а именно в VoxEngine.callPSTN указать второй параметр. Надо учесть что абы какой номер подставлять нельзя. Сначала нужно его авторизовать в разделе CallerIDs
                        0
                        Ага. Ну для начала я должен буду купить номер в разделе Phone Numbers?

                        И еще — сколько одновременных соединений потянет такая схема с двумя вызовами? Если у меня 100-150 звонков в течение дня — это как? Много, мало?
                          0
                          Не обязательно, купить номер — это один из вариантов, второй — валидировать уже существующий у вас, придет автоматизированный вызов с кодом для валидации.

                          Мы не ограничиваем количество вызовов, 100-150 звонков в течение дня — это очень незначительная нагрузка для нашей инфраструктуры :)
                            0
                            Я все-таки решил для тестов купить у вас номер. Купил, указал, для какого приложения он. Но все-равно определяется другой. Как привязать?
                              0
                              В каком формате вы его указываете в методе callPSTN?
                                0
                                Нет-нет, вопрос другой: как сделать, чтобы при звонке на мобильный определялся в качестве обратного номера мой, который я только что купил?
                                  0
                                  Именно так и сделать, указать его вторым параметров в функции callPSTN
                                    0
                                    Разбираемся дальше.

                                    Вообще, крутую штуку вы сделали! Респект! :)
      0
      Здравствуйте. Пытаемся разобраться с вашим сервисом. На странице voximplant.com/docs/references/websdk/ есть vox.addEventListener(VoxImplant.Events.AuthResult, handleAuthResult); vox.login(«voximplant_username», «voximplant_password»). Значит у меня в коде в открытом виде на странице должен быть логин и пароль, это же не безопасно. Как решается вопрос чтобы мои логин-пароль не использовал кто угодно для совершения звонков?
        0
        Ну в целом идея такая, что эти данные клиент должен вводить :) в форме авторизации. Но есть и другой вариант — voximplant.com/docs/references/websdk/VoxImplant.Client.html#loginWithOneTimeKey, вместо пароля используется специальный хэш, который считается у вас на сервере после получения ключа.
          0
          Понял что webSDK удобно, но использовать в нашем случае можно исключительно на свой страх и риск. Смотрим в сторону voximplant.com/docs/references/httpapi/.
        0
        Есть ли пример сценария когда при недозвоне на телефон кол центра идет дозвон на другой и т.д. (т.е. у меня входящих Х номеров в массиве)
          0
          Отлавливаете событие недозвона (http://voximplant.com/docs/references/appengine/CallEvents.Failed.html) и по его факту инициируете следующий вызов. На одну сессию можно сделать не более 10 звонков.
          0
          Выше был вопрос про X номеров колл центра в массиве тут все понятно. А есть ли пример сценария, когда может быть от 1 до N номеров колл центра и 1 номер абонента. Script_custom_data у вас через двоеточие — и в этом сложность. Помню про JSON, но не разобрались с вашим отладчиком. Дадим ваш хорошую загрузку. Помогите разобраться с сценарием.
            +1
            data = VoxEngine.customData();
            data = data.split(":");

            а что мешает data[0] какой нить собрать с разделителями "| " и его снова phones= data[0].split("|"); — получаем массив телефонов
              0
              Можно прямо массив в JSON передать, в чем проблема?
              0
              Андрей, а отладчик у вас сейчас работает? Запускаю ваш же пример, пишет «ожидание начала сессии отладки» и все. Ничего не происходит. Что я делаю не так? Как отладить самописный сценарий без слива денег?
                0
                Отладчик работает, чтобы началась сессия отладки нужно сделать HTTP-запрос StartScenario. Сессия запускается на платформе или по факту HTTP-запроса (в случае callback-сценария) или по факту звонка пришедшего на платформу.
                0
                Разобрались с каскадной переадресацией. Вопрос по записи разговоров voximplant.com/docs/references/appengine/Recorder.html. Сценарий запускает бекенд и получает в ответ media_session_access_url и на этом все. Как бекенду в этом же сеансе получить ссылку на запись разговора?
                  0
                  В сценарии можно получить URL записи из voximplant.com/docs/references/appengine/CallEvents.RecordStarted.html, параметр url. Бэкенд может вызывать функции сценария через media_session_access_url, передавая туда параметры, которые будут приходить сюда voximplant.com/docs/references/appengine/AppEvents.HttpRequest.html, соответственно возвращать данные можно просто сделав return из хэндлера этого события.
                    0
                    Пожалуйста, добавьте в ваш же пример сценария выше (в статье) как с помощью voximplant.com/docs/references/appengine/CallEvents.RecordStarted.html включить запись и по факту отработки получить ссылку на файл. Из сценария ссылку дальше мы самостоятельно с помощью voximplant.com/docs/references/appengine/AppEvents.HttpRequest.html пробросим на свой бекенд.

                    Без примеров кода справится очень затруднительно.
                      0
                      Да вроде все вполне просто (если нужно писать разговор 2х соединенных звонков, то все это делается у любого одного из них):

                      var record_url;
                      call.addEventListener(CallEvents.RecordStarted, handleRecordStarted);
                      call.record();
                      
                      function handleRecordStarted(e) {
                         record_url = e.url;
                      }
                      
                        0
                        Спасибо. Я правильно понимаю, что единственный способ бэкенду получить record_url это повесить в функцию из примера http вызов с передачей параметра? или есть возможность, все же, получить урл с записью после вызова сценария с бэкенда (делаем http запрос для запуска сценария, делаем http запрос для получения урла записи)?
                        т.е. что-то вроде этого (пример для проброса урла записи из сценария на бэкенд)

                        function handleRecordStarted(e) { record_url = e.url; Net.httpRequest("http://somewebservice?url=encodeURIComponent(record_url)", function(e1) { }); }
                          0
                          или есть возможность все же получить урл с записью после вызова сценарий с бэкенда (делаем http запрос для запуска сценарий, делаем http запрос для получения урла записи)?

                          Есть, я же написал что можно делать потом HTTP-запросы к медиа-сессии по адресу media_session_access_url, которые на стороне сценария будут вызывать voximplant.com/docs/references/appengine/AppEvents.HttpRequest.html

                          На стороне сценария будет:
                          VoxEngine.addEventListener(AppEvents.HttpRequest, function (e) {
                              // в e.path будет строка запроса , которая идет после media_session_access_url, можно там передавать название вызываемой функции, как вариант.
                              return record_url;
                          }
                          


                            0
                            ну мы же понимаем, что это не совсем красивое решение. Ибо один итак запущенный процесс (который запускаем сценарий) вместо отработки в самом себе породить еще один, который придет из сценария через AppEvents.HttpRequest

                            Итого: 1 запрос на запуск сценария, второй запрос на вызов нашей функции из сценария через media_session_access_url который породит третий (по счету, а по факту второе соединение) запрос на бэкенд из сценария.

                            По-моему не совсем удачное решение, когда вопрос решается всего то лишь возвращением урла с потенциальной записью при вызове самого сценария там же где приходит media_session_access_url в ответ, а уж существует запись или нет — итог отработки сценария, но мы заранее будем знать где 'если что' искать контент и все в 1 запрос, не правда ли? :)
                              0
                              Вы по-моему неправильно поняли. Запрос функции (media_session_access_url) вернет сразу URL на запись, когда этот URL будет сформирован. Про потенциальный URL даж комментировать не хочется… Вы же понимаете что URL записи формируется только в случае запуска в сценарии записи я надеюсь?
                                0
                                Хотя удобнее вызвать сценарий сразу с флагом записи и получить в ответ адрес где ее можно получить по факту. Это гораздо удобнее для сервисов, которые будут подключены. Тем более все входящее данные при запросе, который включает сценарий у вас все есть. Одно дело глобальные опции, другое дело нюансы сценария, так вот в большинстве случаев глобальной опции на включение записи будет достаточно.
                                  0
                                  Вы плохо себе представляете кол-во сценариев, которые делают на платформе, у вас частный случай и есть все необходимые инструменты для решения задачи. В сценарии может быть до 10 звонков — и какие же из них нужно писать при включении флага?
                                    0
                                    Все правильно. Наш случай это много телефонов колцентра и 1 клиентский, писать нужно сам разговор клиента. Понятно, что ожидание и пробросы на другие звонки колцентра не интересы. Интересует сам разговор, он один (либо он есть либо его нет, поэтому говорю о потенциальном разговоре) Но для удобства использования, глобальную опцию можно вытащить, это частный случай, но думаю, он удобнее в использование будет и не для одного клиента.
                                    Кстати, Спасибо за быстрые ответы.
                                0
                                Ну и в дополнение URL записи всегда можно вытащить из HTTP API уже после завершения звонка, если не горит.

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

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