Перейти к содержанию

WhatsApp

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

Для ведения групповых чатов участники не обязательно должны быть пользователями "Первой Формы". Для пользователей без учетной записи сообщения будут отправлены от имени служебного пользователя (например, 1FWhatsAppBot).

ℹ️ Если в WhatsApp отсутствует бизнес-аккаунт или подписка на chat-api, работа с групповыми чатами будет возможна, только если в них добавлен чат-бот "Первой Формы" и если вручную инициировать с ним диалог.

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

Способы интеграции с WhatsApp

Интеграция возможна с помощью публикации путем отправки запросов либо на бизнес-аккаунт в WhatsApp, либо на шлюз Chat API с платной подпиской (например, https://app.chat-api.com/login)).

Способ 1: Бизнес-аккаунт WhatsApp

Преимущества бизнес-аккаунта WhatsApp:

  • Более высокий уровень безопасности по сравнению с chat-api.

  • Возможность самостоятельно инициировать диалог после постановки задачи в "Первой Форме".

Недостатки бизнес-аккаунта в WhatsApp:

  • Долгая процедура оформления бизнес-аккаунта, которая не всегда завершается успешно.

  • Сложная и недостаточно подробная документация WhatsApp по интеграции.

Способ 2: Подписка на chat-api в WhatsApp

Преимущества подписки на chat-api в WhatsApp:

  • Быстрая процедура оформления.

  • Подробная документация по интеграции.

Недостатки подписки на chat-api в WhatsApp:

  • Платная подписка.

  • Более низкий уровень безопасности по сравнению с бизнес-аккаунтом.

Публикации для отправки запросов в WhatsApp полностью аналогичны публикациям для интеграции с Telegram с использованием WhatsApp Business API.

Способ 3: Сервис WAZZUP

При отсутствии бизнес-аккауната можно настроить интеграцию с WhatsApp при помощи внешнего сервиса WAZZUP.

Шаги по настройке:

  1. Зарегистрируйтесь в Wazzup.

  2. Перейдите в раздел "Каналы", выберите пункт "Добавить канал".

  3. Затем выберите WhatsApp.

  4. Отсканируйте QR-код.

После добавления канал появится в "Списке каналов". К Wazzup можно подключить множество номеров WhatsApp.

ℹ️ Если вы только зарегистрировали аккаунт WhatsApp или перед подключением к сервису зашли в аккаунт с другого телефона — нужно переписываться в мессенджере с телефона в течение суток. Достаточно пообщаться с 10-15 людьми.

ℹ️ Чтобы канал работал стабильно, открывайте WhatsApp на телефоне раз в пару недель. Если этого не делать, WhatsApp разлогинит вас из Wazzup и других связанных устройств. Тогда придется переподключать канал.

  1. Скопируйте ключ API. Он понадобится для подключения из "Первой Формы".

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

  1. Подключитесь к Wazzup из "Первой Формы".

Подключение к Wazzup из "Первой Формы"

Отправка сообщений

Для отправки сообщений необходимо вызвать API-метод POST https://api.wazzup24.com/v3/message

В теле запроса нужно передать:

  • Ключ API в заголовке (см. п.5).

  • Параметры сообщения.

Обязательные параметры запроса представлены в таблице ниже. Со всеми необязательными параметрами можно ознакомиться в официальной документации Wazzup.

Параметр Тип Описание
channelId String Id канала (uuidv4), через который нужно отправить сообщение.
chatType String Тип чата. Доступные значения:

-whatsapp — для индивидуальных чатов в WhatsApp,

-whatsgroup — для групповых чатов в WhatsApp Пример запроса:

local url = 'https://api.wazzup24.com/v3/message'
local parameters = {}
local headers = {}
   headers['Authorization'] = 'Bearer 1f9a06da25944d49bae81ccd105c524d'
   headers['Content-Type'] = 'application/json'
local rawBody = {}
   rawBody['channelId'] = chatInfo.channelId
   rawBody['chatType'] = 'whatsapp'
   rawBody['chatId'] = chatInfo.chatId
   rawBody['text'] = text
   rawBody['crmMessageId'] = tostring(postedComment)
  if sendedCommentInfo.refMessageId ~= nil then
      rawBody['refMessageId'] = sendedCommentInfo.refMessageId
  end
local rawBodyStr = UTILS:json_encode(rawBody):gsub('<br/>', '\\n')
local options = nil
Webhooks

Чтобы подписаться на Webhooks, вызовите метод PATCH https://api.wazzup24.com/v3/webhooks

В теле должен быть JSON с параметрами:

Параметр Тип Описание
webhooksUri String Адрес для получения webhooks. Не более 200 символов
subscriptions Object Настройки webhooks
subscriptions.messagesAndStatuses Boolean Новые сообщения и изменение статуса исходящих
subscriptions.contactsAndDealsCreation Boolean Необходимость создания нового контакта или сделки
subscriptions.channelsUpdates Boolean Изменение статуса канала
subscriptions.tepmplateStatus Boolean Изменение статуса модерации шаблона WABA

При подключении на указанный URL будет отправлен тестовый POST-запрос с телом {test: true }. В ответ сервер должен вернуть 200 при успешном подключении webhooks. Иначе вернется ошибка: "Webhooks request not valid. Response status must be 200".

Примеры смарт-скриптов

Пример отправки ответа по цифре:

function postComment (author, comment, taskid, recip, isQuestion)
  local res = SMART:execute_action('PostComment', taskid, 'task', {
       CommentAuthor = author,
       CommentText = comment,
       Task = taskid,
       Recipients = recip,
       RecipientGroups = nil,
       ForcedEmail = false,
       CommentSMS = false,
       NoSubscription = false,
       TextAsHTML = false,
       MarkAsQuestion = isQuestion,
       RecipCopies = nil,
       CommentType = 3,
       CommentVisibleOnlyToRealRecipients = false,
       SilentComment = false,
       RealUserId = nil
   })
  return res
end

--получаем контент входящего комментария
local commentContent = SQL:scalar(
   [
       select c.Content
       from Comments c (nolock)
       where c.CommentID = @commentid
   ]],
   {
       commentid = EVENTPARAMS['CommentId']
   }
)
--var_dump(commentInfo)

local text = ''
if commentContent == '1' then
   text = 'Если вы хотите сделать расчет заказа - заполните форму https://www.grandline.ru/services/zakazat-raschet/. Если у вас остались вопросы - напишите их в ответном сообщении оператору.'
elseif commentContent == '2' then
   text = 'Если вы хотите узнать стоимость и наличие товара - посмотрите в нашем интернет-магазине https://www.grandline.ru/katalog/. Если у вас остались вопросы - напишите их в ответном сообщении оператору.'
elseif commentContent == '3' then
   text = 'Если вы хотите узнать адреса точек продажи - прейдите по ссылке https://www.grandline.ru/gde-kupit/. Если у вас остались вопросы - напишите их в ответном сообщении оператору.'
elseif commentContent == '4' then
   text = 'Если у вас вопрос по документам - перейдите по ссылке https://www.grandline.ru/dokumenty/. Если у вас остались вопросы - напишите их в ответном сообщении оператору.'
end

local postedComment = postComment(2110, text, CONTEXT["Id"], {2349}, false)

--получаем инфо об отправленном комментарии
local sendedCommentInfo = SQL:query_one(
   [[
       select
           c.CommentID,
           u.DisplayName,
           --c.Content,
           iif(u.userid <> 2110, '[' + u.DisplayName + ' ', '') + c.Content as message,
           c.InReplyToCommentID,
           lg.messageId as refMessageId
       from Comments c (nolock)
       join Users u (nolock) on u.UserID = c.UserID
       left join cm_whatsapp_webhooks_log lg (nolock)
         on lg.commentId = c.InReplyToCommentID
       where c.CommentID = @commentid
   ]],
   {
       commentid = postedComment
   }
)

--получаем инфо по задаче чата
local chatInfo = SQL:query_one(
   [
       select
           kl.extparam177value as surname,
           kl.extparam171value as name,
           t.extparam888value as channelId,
           t.extparam889value as chatId
       from tasksinsubcat117denormalized t (nolock)
       join tasksinsubcat49denormalized kl (nolock)
           on kl.taskid = t.extparam580nativevalue
       where t.taskid = @contextid
   ]],
   {
       contextid = CONTEXT["Id"]
   }
)
--var_dump(chatInfo)

--параметры запроса
local url = 'https://api.wazzup24.com/v3/message'
local parameters = {}
local headers = {}
   headers['Authorization'] = 'Bearer 1f9a06da25944d49bae81ccd105c524d'
   headers['Content-Type'] = 'application/json'
local rawBody = {}
   rawBody['channelId'] = chatInfo.channelId
   rawBody['chatType'] = 'whatsapp'
   rawBody['chatId'] = chatInfo.chatId
   rawBody['text'] = text
   rawBody['crmMessageId'] = tostring(postedComment)
  if sendedCommentInfo.refMessageId ~= nil then
      rawBody['refMessageId'] = sendedCommentInfo.refMessageId
  end
local rawBodyStr = UTILS:json_encode(rawBody):gsub('<br/>', '\\n')
local options = nil

-- var_dump(rawBodyStr)

--логируем в базу запрос
local insertedId = SQL:scalar(
   [[
       insert into cm_whatsapp_webhooks_log
       select
           getdate(),  --date
           1,          --direction
           null,       --webhook
           @message,   --messages
           null,       --statuses
           null,       --messageId
           null,       --status
           null,       --isEcho
           @commentId  --commentId

       select IDENT_CURRENT('cm_whatsapp_webhooks_log')
   ]]
   {
       commentId = postedComment,
       message = rawBody
   }
)

local response = HTTP:send_http_request("POST", url, parameters, headers, rawBodyStr, options)
local responseMessageId = UTILS:json_decode(response["HttpResponse"]["ResponseContent"])
local postedMessageId = responseMessageId["messageId"]

--если ID есть, то обновляем комментарии
if postedMessageId ~= nil then
   SQL:query(
       'update cm_whatsapp_webhooks_log set messageId = @postedMessageId where commentId = @commentId',
       {commentId = postedComment, postedMessageId = postedMessageId}
   )
end
Пример отправки комментария в WhatsApp:

--получаем инфо о комментарии
local commentInfo = SQL:query_one(
   [[
       select
           c.CommentID,
           u.DisplayName,
           --c.Content,
           iif(u.userid <> 2110, '[' + u.DisplayName + ' ', '') + c.Content as message,
           c.InReplyToCommentID,
           lg.messageId as refMessageId
       from Comments c (nolock)
       join Users u (nolock) on u.UserID = c.UserID
       left join cm_whatsapp_webhooks_log lg (nolock)
         on lg.commentId = c.InReplyToCommentID
       where c.CommentID = @commentid
   ]],
   {
       commentid = EVENTPARAMS['CommentId']
   }
)

--получаем инфо по задаче чата
local chatInfo = SQL:query_one(
   [
       select
           kl.extparam177value as surname,
           kl.extparam171value as name,
           t.extparam888value as channelId,
           t.extparam889value as chatId
       from tasksinsubcat117denormalized t (nolock)
       join tasksinsubcat49denormalized kl (nolock)
           on kl.taskid = t.extparam580nativevalue
       where t.taskid = @contextid
   ]],
   {
       contextid = CONTEXT["Id"]
   }
)
-- var_dump(chatInfo)

--параметры запроса
local url = 'https://api.wazzup24.com/v3/message'
local parameters = {}
local headers = {}
   headers['Authorization'] = 'Bearer 1f9a06da25944d49bae81ccd105c524d'
   headers['Content-Type'] = 'application/json'
local rawBody = {}
   rawBody['channelId'] = chatInfo.channelId
   rawBody['chatType'] = 'whatsapp'
   rawBody['chatId'] = chatInfo.chatId
   rawBody['text'] = commentInfo.message
   rawBody['crmMessageId'] = tostring(EVENTPARAMS['CommentId'])
  if commentInfo.refMessageId ~= nil then
       rawBody['refMessageId'] = commentInfo.refMessageId
  end
local rawBodyStr = UTILS:json_encode(rawBody):gsub('<br/>', '\\n')
local options = nil

--логируем в базу запрос
local insertedId = SQL:scalar(
   [[
       insert into cm_whatsapp_webhooks_log
       select
           getdate(), --date
           1,         --direction
           null,       --webhook
           @message, --messages
           null,       --statuses
           null,       --messageId
           null,       --status
           null,     --isEcho
           @commentId --commentId

       select IDENT_CURRENT('cm_whatsapp_webhooks_log')
   ]]
   {
       commentId = EVENTPARAMS['CommentId'],
       message = rawBody
   }
)

local response = HTTP:send_http_request("POST", url, parameters, headers, rawBodyStr, options)
--var_dump(response)
--временное логирование при отправке
SQL:query(
   'update cm_whatsapp_webhooks_log set statuses = @response where commentId = @commentId',
   {commentId = EVENTPARAMS['CommentId', response = response}
)
local responseMessageId = UTILS:json_decode(response["HttpResponse"]["ResponseContent"])
local postedMessageId = responseMessageId["messageId"]

--если ID есть, то обновляем комментарии
if postedMessageId ~= nil then
   SQL:query(
       'update cm_whatsapp_webhooks_log set messageId = @postedMessageId where commentId = @commentId',
       {commentId = EVENTPARAMS['CommentId', postedMessageId = postedMessageId}
   )
end
Пример отправки запроса с вложением в WhatsApp:

CTX = CONTEXT["Id"]
FILEID = EVENTPARAMS["FileId"]["Id"]

local fileInfo = SQL:scalar(
   [
       select
      --fs.fileid,
       replace(convert(nvarchar(36), fs.GUID), '-', '')
       from FileStorageFileToTaskLinks tl (nolock)
       join FileStorageFiles fs (nolock) on fs.FileID = tl.FileId
       join Tasks t (nolock) on t.TaskID = tl.TaskId
       where t.SubcatID = 117
       and fs.fileid = @fileid
   ]],
   {
       fileid = FILEID
   }
)
-- var_dump(fileInfo)

local chatInfo = SQL:query_one(
   [
       select
           kl.extparam177value as surname,
           kl.extparam171value as name,
           t.extparam888value as channelId,
           t.extparam889value as chatId
       from tasksinsubcat117denormalized t (nolock)
       join tasksinsubcat49denormalized kl (nolock)
           on kl.taskid = t.extparam580nativevalue
       where t.taskid = @contextid
   ]],
   {
       contextid = CTX
   }
)
-- var_dump(chatInfo)

local url = 'https://api.wazzup24.com/v3/message'
local parameters = {}
local headers = {}
   headers['Authorization'] = 'Bearer 1f9a06da25944d49bae81ccd105c524d'
   headers['Content-Type'] = 'application/json'
local rawBody = {}
   rawBody['channelId'] = chatInfo.channelId
   rawBody['chatType'] = 'whatsapp'
   rawBody['chatId'] = chatInfo.chatId
   rawBody['contentUri'] = 'https://f1.grandline.ru/app/v1.2/api/publications/action/sync_whatsapp_getattachment?file=' .. fileInfo

local rawBodyStr = UTILS:json_encode(rawBody)
local options = nil
local response = HTTP:send_http_request("POST", url, parameters, headers, rawBodyStr, options)
local responseMessageId = UTILS:json_decode(response["HttpResponse"]["ResponseContent"])
local postedMessageId = responseMessageId["messageId"]
Пример получения Webhooks WhatsApp:

SYS_ROBOT = 3
WA_ROBOT = 2349 --пользователь whatsapp_robot
WA_SUBCAT = 117 --категория чатов
CONTACT_SUBCAT = 49 --категория контактных лиц
PARAMS = UTILS:json_decode(EVENTPARAMS["PublishedObjectParameters"]); --json входящих параметров

function createTask (owner, subcat, tasktext, performers, extparams)
   local res = SMART:execute_action("CreateTask", nil, "task",
       {
           Owner = owner,
           Subcat = subcat,
           TaskText = tasktext,
           CreateLink = false,
           CreateSubtask = false,
           Performers = performers,
           DueTime = nil,
           TaskStartTime = nil,
           ExtParams = extparams,
           NewTaskCopySubscribers = false,
           CreateCopyFiles  = false,
           CopyParentText  = false,
           Priority  = 1,
           Notify = nil,
           UsersToSubscribe = nil,
           CreateCopiesForEachPerformer  = false,
           LinkFiles = nil,
           AssignLetterWithTask = nil,
           Confidentiality = nil,
           LinkAsUser = nil
       }
   )
   return res
end

function postComment (comment, taskid, recip, isQuestion)
   local res = SMART:execute_action('PostComment', taskid, 'task', {
       CommentAuthor = WA_ROBOT,
       CommentText = comment,
       Task = taskid,
       Recipients = recip,
       RecipientGroups = nil,
       ForcedEmail = false,
       CommentSMS = false,
       NoSubscription = false,
       TextAsHTML = false,
       MarkAsQuestion = isQuestion,
       RecipCopies = nil,
       CommentType = 3,
       CommentVisibleOnlyToRealRecipients = false,
       SilentComment = false,
       RealUserId = nil
   })
   return res
end

--postComment (PARAMS, 925710, nil, false)
--[=[
SMART:execute_action('PostComment', 925710, 'task', {
   CommentAuthor = 3,
   CommentText = EVENTPARAMS["PublishedObjectParameters"],
   Task = 925710,
   Recipients = nil,
   RecipientGroups = nil,
   ForcedEmail = false,
   CommentSMS = false,
   NoSubscription = false,
   TextAsHTML = false,
   MarkAsQuestion = false,
   RecipCopies = nil,
   CommentType = 3,
   CommentVisibleOnlyToRealRecipients = false,
   SilentComment = false,
   RealUserId = nil
})
=]]

-- Обработка входящего вебхука с ошибкой
if PARAMS["requestBody"]["messages"] ~= nil and PARAMS["requestBody"]["messages"][1]["status"] == 'error' then]
  -- Логирование в базу
   INSERTED_ID = SQL:scalar(
       [[
           insert into cm_whatsapp_webhooks_log
           select
               getdate(), --date
               0, --direction - входящее
               @webhook, --webhook
               JSON_QUERY(@webhook, '$.requestBody.messages'), --messages
               JSON_QUERY(@webhook, '$.requestBody.statuses'), --statuses
               isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].messageId'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].messageId')), --messageId
               isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].status'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].status')), --status
               cast(JSON_VALUE(@webhook, '$.requestBody.messages[0].isEcho') as bit), --isEcho
               null --commentId

           --select @@identity
           select IDENT_CURRENT('cm_whatsapp_webhooks_log')
       ]],
       {webhook = PARAMS}
   )
   -- Получаем задачу чата
   local chatTask = SQL:scalar(
       [
           select t.taskid
           from tasksinsubcat117denormalized t (nolock)
           join Comments c (nolock)
               on c.taskid = t.taskid
           join cm_whatsapp_webhooks_log lg (nolock)
               on lg.commentid = c.commentid
           where lg.messageid = @messageid
           and t.extparam888value = @channelId
           and t.extparam889value = @chatid
           and t.isclosed = 0
       ]],
       {
           messageid = PARAMS["requestBody"]["messages"][1]["messageId"],
           chatid = PARAMS["requestBody"]["messages"][1]["chatId"],
           channelId = PARAMS["requestBody"]["messages"][1]["channelId"]
       }
   )
   local errText = 'Возникла непредвиденная ошибка при получении данных из WhatsApp.'
   if PARAMS["requestBody"]["messages"][1]["error"]["error"] == 'BAD_CONTACT' then
  --local errText = PARAMS["requestBody"]["messages"][1]["error"]["error"] .. '. ' .. PARAMS["requestBody"]["messages"][1]["error"]'description']{.f_CodeExample style="color: #008000;"}
       errText = 'WhatsApp на данном номере не зарегистрирован.'
   end
  --Комментарий об ошибке
   postComment (errText, chatTask, nil, false)
  --Отклоняем задачу чата
   SMART:execute_action('SetState', chatTask, 'task', {
       Task = chatTask,
       Steper = WA_ROBOT,
       State = 2,
       ThrowErrorOnNoStep = true,
       Reason = nil,
       BreakTaskLinkForAll = false
   })
end

-- Обработка входящего вебхука со статусами
if PARAMS["requestBody"]["statuses"] ~= nil then]
   -- Логирование в базу
   INSERTED_ID = SQL:scalar(
       [[
           insert into cm_whatsapp_webhooks_log
           select
               getdate(), --date
               0, --direction - входящее
               @webhook, --webhook
               JSON_QUERY(@webhook, '$.requestBody.messages'), --messages
               JSON_QUERY(@webhook, '$.requestBody.statuses'), --statuses
               isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].messageId'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].messageId')), --messageId
               isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].status'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].status')), --status
               cast(JSON_VALUE(@webhook, '$.requestBody.messages[0].isEcho') as bit), --isEcho
               null --commentId

           --select @@identity
           select IDENT_CURRENT('cm_whatsapp_webhooks_log')
       ]],
       {webhook = PARAMS}
   )

   if PARAMS["requestBody"]["statuses"][1]["status"] == 'read' then
       local commentid = SQL:scalar(
           [[
               select top 1 commentId
               from cm_whatsapp_webhooks_log
               where messageId = @messageId
               and commentid is not null
           ]]
           {messageid = PARAMS["requestBody"]["statuses"][1"messageId"}
       )
       if commentid ~= nil then
           SQL:query(
               [[
                   update commentrecipients
                   set isunread = 0, readdate = getdate()
                   where commentid = @commentid
               ]]
               {commentid = commentid}
           )
       end
   end
end

-- Обработка входящего вебхука
if PARAMS["requestBody"]["messages"] ~= nil and PARAMS["requestBody"]["messages"][1]["status"] == 'inbound' then]

  -- Логирование в базу
   INSERTED_ID = SQL:scalar(
       [[
           insert into cm_whatsapp_webhooks_log
           select
               getdate(), --date
               0, --direction - входящее
               @webhook, --webhook
               JSON_QUERY(@webhook, '$.requestBody.messages'), --messages
               JSON_QUERY(@webhook, '$.requestBody.statuses'), --statuses
               isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].messageId'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].messageId')), --messageId
               isnull(JSON_VALUE(@webhook, '$.requestBody.messages[0].status'), JSON_VALUE(@webhook, '$.requestBody.statuses[0].status')), --status
               cast(JSON_VALUE(@webhook, '$.requestBody.messages[0].isEcho') as bit), --isEcho
               null --commentId

           --select @@identity
           select IDENT_CURRENT('cm_whatsapp_webhooks_log')
       ]],
       {webhook = PARAMS}
   )
  -- INSERTED_ID = 701

   -- Получение сообщений из вебхука
   MESSAGES = PARAMS["requestBody"]["messages"]
   if MESSAGES ~= nil then
       CHANNEL_ID = MESSAGES[1].channelId
       CONTACT_NAME = MESSAGES[1].contact.name
       CHAT_ID = MESSAGES[1].chatId

     -- Проверка контактного лица по телефону
       CONTACT_TASK_ID = SQL:scalar(
           [
               select t49.taskid
               from tasksinsubcat47denormalized t47 (nolock)
               join extparamtable565denormalized tb (nolock)
                   on tb.Column58NativeValue = t47.taskid
               join tasksinsubcat49denormalized t49 (nolock)
                   on t49.taskid = tb.taskid
               where t47.extparam73value = @phone
               and isnull(t49.extparam94value, '') <> ''

             --select tb.taskid
               --from extparamtable1141denormalized tb (nolock)
               --where tb.column110value = @phone
           ]],
           {phone = CHAT_ID}
       )
       if CONTACT_TASK_ID == nil then
         -- Создание новое контактное лицо
           local contactExtParams = {}
               contactExtParams[1] = '{"ExtParamId": 171, "FixedValue": "' .. CONTACT_NAME .. '"}'
               contactExtParams[2] = '{"ExtParamId": 613, "FixedValue": "' .. CHAT_ID .. '"}'
             --contactExtParams[3 = '+{111:{"First":"' + ДП1 + '"}, 222:{"First":"'+ ВСтроку(ДП2) +'"} }']{.f_CodeExample style="color: #008000;"}
           local createdContact = createTask (WA_ROBOT, CONTACT_SUBCAT, "", {}, contactExtParams)
           CONTACT_TASK_ID = createdContact[0]
       end -- Конец создания контактного лица
       -- var_dump(CONTACT_TASK_ID)

       -- Получение данных контактного лица по телефону
       CONTACT_INFO = SQL:query_one(
           [
               select
                   t47.taskid as identTaskid,
                   t49.taskid as contactTaskid,
                 -- t49.extparam177value as contactLastname,
                   -- t49.extparam171value as contactName,
                   iif(t49.extparam177value is null or t49.extparam177value = '', t49.extparam171value, t49.extparam177value + ' ' + t49.extparam171value) as contactName,
                   t49.extparam271value as contactResponsible,
                   t49.extparam271nativevalue as contactResponsibleTaskId,
                   t30.extparam367nativevalue as contactResponsibleId
               from tasksinsubcat47denormalized t47 (nolock)
               join extparamtable565denormalized tb (nolock)
                   on tb.Column58NativeValue = t47.taskid
               join tasksinsubcat49denormalized t49 (nolock)
                   on t49.taskid = tb.taskid
               left join tasksinsubcat30denormalized t30 (nolock)
                   on t30.taskid = t49.extparam271nativevalue
               where t47.extparam73value = @phone
           ]],
           {phone = CHAT_ID}
       )
      -- var_dump(CONTACT_INFO)

       -- Получаем задачу чата, если она существует
       local chatTaskInfo = SQL:query_one(
           [
               select t.taskid, t.stateid
               from tasksinsubcat117denormalized t (nolock)
               where t.extparam580nativevalue = @contact
               and t.extparam888value = @channelId
               and t.extparam889value = @chatid
               and t.isclosed = 0
           ]],
           {contact = CONTACT_TASK_ID, chatid = CHAT_ID, channelId = CHANNEL_ID}
       )
     -- CHAT_TASK_ID = chatTaskInfo.taskid
       -- CHAT_STATE_ID = chatTaskInfo.stateid
       -- var_dump(CHAT_TASK_ID)
       -- var_dump(CHAT_STATE_ID)

       -- Если задачи чата нет, то создаем ее
       if chatTaskInfo == nil then
           local chatTaskText = CONTACT_INFO.contactName .. ' (' .. CHAT_ID .. ')'
           local chatPerformers = {}
               chatPerformers[1] = WA_ROBOT
           local chatExtParams = {}
               chatExtParams[1] = '{"ExtParamId": 580, "FixedValue": ' .. CONTACT_TASK_ID .. '}'
               chatExtParams[2] = '{"ExtParamId": 888, "FixedValue": "' .. CHANNEL_ID .. '"}'
               chatExtParams[3] = '{"ExtParamId": 889, "FixedValue": "' .. CHAT_ID .. '"}'
               if CONTACT_INFO.contactResponsibleTaskId ~= nil then
                   chatExtParams[4] = '{"ExtParamId": 271, "FixedValue": ' .. CONTACT_INFO.contactResponsibleTaskId .. '}'
               end
           local createdTask = createTask (SYS_ROBOT, WA_SUBCAT, chatTaskText, chatPerformers, chatExtParams)
           CHAT_TASK_ID = createdTask[0]
           CHAT_STATE_ID = 1
       else
           CHAT_TASK_ID = chatTaskInfo.taskid
           CHAT_STATE_ID = chatTaskInfo.stateid
       end -- Конец создания задачи чата

       -- адресат комментария и вопрос в зависимости от статуса
       recipients = {}
       isQuestion = true

       if CHAT_STATE_ID ~= 99 then
           table.insert(recipients, WA_ROBOT)
           isQuestion = false
       else
           if CONTACT_INFO.contactResponsibleId ~= nil then
               table.insert(recipients, CONTACT_INFO.contactResponsibleId)
           else
               local usersInGroup928 = SQL:query('select ug.userid from usergroups ug (nolock) where ug.groupid = 928', {})
               for k, v in pairs(usersInGroup928) do
                   table.insert(recipients, v.userid)
               end
           end
           isQuestion = false
       end

      -- Обработка сообщений
       for num, message in pairs(MESSAGES) do
         -- на всякий случай проверим на скобки
           -- local msgchunked = message:gsub('^%[', ''):gsub('%$', '')]{.f_CodeExample style="color: #008000;"}
           -- local msg = UTILS:json_decode(msgchunked)

           local isEcho = message.isEcho
           local msgType = message.type
           local msgText = message.text
           local msgAuthorName = message.authorName
           local quotedMsgId = nil
           if message.quotedMessage ~= nil then
               quotedMsgId = message.quotedMessage.messageId
           end
           local msgContentUri = nil
           local msgFileName = nil
           if message.contentUri ~= nil then
               msgContentUri = message.contentUri
               msgFileName = message.contentUri:match('%=(.+%.%w+)$')
              -- ext2 = str:match('%.(%w+)$')
           end

         -- если сообщение типа текст
           if msgType == 'text' and msgText ~= '' and msgText ~= nil then
               if CHAT_STATE_ID == 1 then

                   postedComment = postComment (msgText, CHAT_TASK_ID, recipients, isQuestion)
                   SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})
                   SMART:execute_action('MakeStep', CHAT_TASK_ID, 'task', {
                       Task = CHAT_TASK_ID,
                       Steper = SYS_ROBOT,
                       Step = 926,
                       Reason = nil,
                       DoNotWriteComment = false
                   })

               elseif CHAT_STATE_ID == 98 and msgText ~= '1' and msgText ~= '2' and msgText ~= '3' and msgText ~= '4' then

                   SMART:execute_action('MakeStep', CHAT_TASK_ID, 'task', {
                       Task = CHAT_TASK_ID,
                       Steper = WA_ROBOT,
                       Step = 927,
                       Reason = nil,
                       DoNotWriteComment = false
                   })
                 --CHAT_STATE_ID = 99

                   recipients = {}
                   if CONTACT_INFO.contactResponsibleId ~= nil then
                       table.insert(recipients, CONTACT_INFO.contactResponsibleId)
                   else
                       local usersInGroup928 = SQL:query('select ug.userid from usergroups ug (nolock) where ug.groupid = 928', {})
                       for k, v in pairs(usersInGroup928) do
                           table.insert(recipients, v.userid)
                       end
                   end
                   isQuestion = true
                   postedComment = postComment (msgText, CHAT_TASK_ID, recipients, isQuestion)
                   SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})

                   SMART:execute_action('PostComment', CHAT_TASK_ID, 'task', {
                       CommentAuthor = 2110,
                       CommentText = 'Ваш вопрос направлен менеджеру',
                       Task = CHAT_TASK_ID,
                       Recipients = {2349},
                       RecipientGroups = nil,
                       ForcedEmail = false,
                       CommentSMS = false,
                       NoSubscription = false,
                       TextAsHTML = false,
                       MarkAsQuestion = false,
                       RecipCopies = nil,
                       CommentType = 3,
                       CommentVisibleOnlyToRealRecipients = false,
                       SilentComment = false,
                       RealUserId = nil
                   })

               else

                   postedComment = postComment (msgText, CHAT_TASK_ID, recipients, isQuestion)
                   SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})

               end

              --msgText = '[' .. contactInfo.contactName .. ' ' .. msgText]{.f_CodeExample style="color: #008000;"}
               --postedComment = postComment (msgText, CHAT_TASK_ID, recipients, isQuestion)
               --SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})
           end

          -- если сообщение типа image, audio, video, document
           if msgType == 'image' or msgType == 'audio' or msgType == 'video' or msgType == 'document' then
             --скачивание файла
               local download = SMART:execute_action('DownloadFile', CHAT_TASK_ID, 'task', {
                   Task = CHAT_TASK_ID,
                   UploadingUserName = WA_ROBOT,
                   TargetExternalParameter = nil,
                   FileLink = msgContentUri,
                   Comment = nil,
                   DownloadManyFiles = false,
                   FileMask = nil,
                   FileCreateDateFrom = nil,
                   FileCreateDateTo = nil,
                   FileName = msgFileName,
                   HttpMethod = nil,
                   HttpParameters = {},
                   HttpHeaders = {},
                   HttpRequestBody = nil
               })
               local fileId = UTILS:json_decode(download)["Id"]
               postedComment = SQL:scalar('select CommentId from FileStorageFileToCommentLinks (nolock) where FileId = @fileid and Taskid = @taskid', {fileid = fileId, taskid = CHAT_TASK_ID})

               if msgText ~= nil and msgText ~= '' then
                 --msgText = '[' .. contactInfo.contactName .. ' ' .. msgText]{.f_CodeExample style="color: #008000;"}
                   -- postedComment = postComment (msgText, CHAT_TASK_ID, recipients, isQuestion)
                   -- SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})

                   SQL:query('update comments set content = @content, typeid = 3, eventid = null where commentid = @commentid', {commentid = postedComment, content = msgText})
                  -- SQL:query('update cm_whatsapp_webhooks_log set commentId = @commentid where id = @logId', {commentid = postedComment, logId = INSERTED_ID})
               end
               SQL:query('update cm_whatsapp_webhooks_log set commentId = @postedComment where id = @logId', {postedComment = postedComment, logId = INSERTED_ID})
           end --msgType == 'image'

         -- Если есть цитированное сообщение
           if quotedMsgId ~= nil then
               --ищем ID комментария в логе
               local replyComment = SQL:scalar('select commentId from cm_whatsapp_webhooks_log (nolock) where messageId = @quotedMessageId', {quotedMessageId = quotedMsgId})
               --если ID есть, то обновляем комментарии
               if replyComment ~= nil then
                   SQL:query('update Comments set InReplyToCommentID = @replyComment where CommentID = @postedComment', {postedComment = postedComment, replyComment = replyComment})
               end
           end -- Конец цитирования
       end -- Конец цикла по сообщениям
   end -- Конец if MESSAGES
end -- Конец обработки входящего вебхука
Пример интеграции

Предварительная настройка

Создается задача в справочнике контактных лиц: имя и телефон должны совпадать с данными в WhatsApp. Затем создается задача в категории "Общение WhatsApp" с параметрами:

  • Контактное лицо -- тип Lookup поле, категория-источник "Контактные лица".

  • Телефон -- тип "Текст", телефон нового контакта.

  • ID чата -- тип "Текст", ID чата из Wazzup (соответствует номеру телефона). Заполняется автоматически.

  • ID канала -- тип "Текст",  ID канала из Wazzup (для всех сообщений с одного канала одинаковый). Заполняется автоматически.

Если в "Первой Форме" нет активной задачи с этим контактом в категории "Общение WhatsApp" создастся новый чат и задача перейдет в статус "Диалог с ботом",  а пользователю отправляется автоматическое сообщение. Если есть активный чат, то в нем отобразится комментарий.

Общение с ботом

Пока задача чата находится в статусе "Диалог с ботом", на сообщения с кодом "1", "2", "3" или "4" будет возвращаться соответствующий запросу автоматический ответ.

Если пользователь отправит сообщение с иным текстом, задача чата будет переведена на другой статус в соответствии с бизнес-процессом.

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

Сообщения клиента из WhatsApp отображаются в комментариях задачи от имени пользователя WhatsAppRobot.

В WhatsApp клиента сообщения из "Перфой Формы" приходят с указанием ФИО менеджера, написавшего комментарий.

Ответ на входящее сообщение

В комментариях к задаче в "Первой Фоме" выбирается необходимое сообщение от пользователя WhatsAppRobot и на него отправляется ответ.

В WhatsApp комментарий отображается как ответ на сообщение.

Аналогично в обратную сторону: в WhatsApp сотрудник отвечает на сообщение, а в "Первой Форме" это отображается как ответ на комментарий.

Вложения

Приложенный файл или картинка в WhatsApp после отправки отображается в "Первой Форме" как вложение к комментарию.

Аналогично в обратную сторону: вложенный в комментарий к задаче "Общение WhatsApp" файл или картинка после отправки отображаются в WhatsApp.