Call logging

If you choose to provide the call logging, then your integration should register calls between contacts and managers and link each call to the suitable entity ( contact, company, lead) according to our call logging algorithm or to the manager’s will.

When a call between a customer and a manager ends, we define three major activities:

  1. If configured, a webhook about ending the call should be launched. It comes from the VoIP service. Then the integration backend should affect one of the lead/customer/contact/company, or create an incoming lead if no entity connected to the phone number exists.
  2. If the call was made in Kommo, a modal window to add the call result should appear. When the manager enters data about the call and saves it, the call result is saved into Kommo as a note. If the manager decides not to save, no call result will be added.
  3. A call recording should be linked to the call card, if there is a contact connected to this call or it has been created within 18 hours. The recording comes from VoIP service. If there is no contact, the call recording will be deleted.

The following diagram represents the ideal situation; a call came, a modal window appeared and the call took place. When ending the call, a call with both note and call recording has been added to a chosen entity. No duplicate calls or ignored incoming leads exist. In this situation, calling inside Kommo is activated, both call recording and webhook sending are provided from the VoIP service.

foto

As you see the existence of the three tasks is not mandatory, but the main task you should care about is the synchronization of handling the requests, in order that all three or any group of them should affect one and only one entity. Requests could come at the same time or only from the modal or only from the webhook. Also, our API does not regulate the identity of calls.

In either case we define an essential use case to log a call, whether it came first from the webhook or from the call result window.

One of the possible solutions to handle these three asynchronous requests is to write information about the added note to the database to regulate duplication, and by using an external queue server like Beanstalk to store the tasks. The core business logic app submits a task to the queue with a specific handler name. The queue server is configured to know about handlers and workers according to the task property. It provides the ability to delay the execution of a task under certain conditions. We recommend using Beanstalk as a queue server, because of its performance, reliability and simplicity.

We implemented the use case in this hierarchy

VoIP-Integration
├── KommoAuthentication
├── Database
├── Queue
├── Widget
├── Voip
│   ├── Task
│   │   └── AddCallTask
│   └── UseCase
│       └── AddCallUseCase
└── Console
    └── Worker
        └── AddCallWorker

All calls should be grabbed from the VoIP service and stored in the calls repository in the integration database. A function to choose a call by its identifiers should be implemented.

public static function getByCallIdAndKommoAccountId(string $callId, int $KommoAccountId): voipCalls{
    //voipCalls database table model
    return voipCalls::query() 
		->where('call_id', '=', $callId)
		->where('Kommo_account_id', '=', $KommoAccountId)
		->first();
}

To log each call, a task is created. Then we send tasks to the queue, and we grab them from the queue using workers. The constructor of the task is shown below.

//AddCallTask
public function __construct(
	protected int $KommoAccountId,
	protected string $callId,
);

The corresponding use case takes the task, and performs it. The call is added to the Kommo account using the call logging API. This use case will be performed once we know about a new call, whether from the webhook or from the modal.

//AddCallUseCase
public function handle(AddCallTask $task): void
{
	$voipCall = voipCalls::getByCallIdAndKommoAccountId(
		$task->getCallId(),
		$widgetSettings->getKommoAccountId()
	);

	$call = Call::fromModel($voipCall);//Call entity

	if ($record = $voipCall->getRecording() ?? '') {
            $record = sprintf(
                '%s/voip/%s/get_call_record/%s',
                $this->appConfig->getBaseUrl(),
                $widgetSettings->getKommoAccountId(),
                $call->getCallId()
            );
	}
	$call->setRecordLink($record);

	if ($voipCall->getEntityId() !== null) {
		throw new WorkerException('Already created');
	}

	//Call our client API to create call event 
	$KommoCall = $this->KommoApiClient->calls()->addOne($call);

	//Create an incoming lead if no entity was found by number i.e callModel is null
	if ($callModel === null && $voipCall->getDirection() === CallType::INBOUND) {
		//call our client API to create incoming lead
		$incomingLead = $apiClient->makeUnsorted(
 			$call,
			$this->OAuthConfig->getWidgetCode(),
			$widgetSettings->getPipelineId(),
			$voipCall->getResponsibleUserId()
		);
		$incomingLeadUid = $incomingLead->getUid();
		$KommoCall = $this->KommoApiClient->calls()->addOne($call);
	}
	$task->setSuccess(true);
}

Worker grabs a task from the queue, it has a Kommo account id and a call id. Search for the call in the integration database. If it’s already added then you shouldn’t add it.

//AddCallWorker
public function run(array $data, LoggerInterface $logger): void
{
	$taskData = $data['data'];
	$task = new AddCallTask(
		$taskData['account_id'],
		$taskData['call_id'],
	);
	$this->addCallUseCase->handle($task);
	if (!$task->isSuccess()) {
		throw BusinessLogicException::create('Create call event error');
	}
}

Next, we will discuss the action that could be done when receiving the webhook.