Call logging

If you choose to provide call logging for your integration it's crucial to register calls between clients and managers and link each call to the corresponding entity (contact, company, lead) according to the call logging algorithm (or manager's choice).

📘

In this documentation we will cover implementation of VoIP services with Kommo via webhook which is one of the possible options for such integrations. Anyway, you can use a different logic according to the service you are going to integrate.

Call logging flow

When a call between a client and a manager ends, there might be the following major actions:

  1. A webhook about ending the call is launched from the VoIP service. Then the integration backend needs to find an appropriate entity (lead, contact or company), or create an incoming lead if no entity connected to the phone number exists in your Kommo account.
  2. If there is a contact with the phone number, you can add call note with all calling data you need and link it with the contact via API.
  3. If the call was ended from Kommo, you can render a modal window to add extra data on call result from a manager. If the manager fills in the call data and saves the result, the data is passed to the VoIP integration backend and it creates call note in the entity card alongside with the call recording from the webhook.

The following scheme represents the sample flow of VoIP integration in action. Upon receiving a call, a modal window appears and the call session begins. When ending the call, it is added with both note and call recording to the specified entity. Nor duplicate calls neither ignored incoming leads should be passed to Kommo through your integration. In the example below, calling inside Kommo feature is activated, both call recording and webhook sending are provided from the VoIP service.

The presence of the steps is not mandatory. However, the primary focus should be on synchronizing request handling to ensure that all steps - or any subset of them - affect only one entity. Requests may arrive simultaneously, or originate solely from the modal or the VoIP system (e.g., via webhook). It's also important to note that Kommo API does not manage call identity.

👍

In any case, we consider call logs essential, regardless of whether they come first from the webhook or from the call result window.

One of the possible solutions to handle these asynchronous requests is to write information about the added note to the database to regulate duplication, and by using an external queue server 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 Beanstalkd, Apache Kafka or RabbitMQ as a queue server because of their performance, reliability and simplicity.

We implemented the use case in this hierarchy:

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 {
    // voip_calls database table model
    return VoipCalls::query() 
        ->where('call_id', '=', $callId)
        ->where('kommo_account_id', '=', $kommoAccountId)
        ->first();
}

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

public function __construct(
    private int $kommoAccountId,
    private string $callId,
);

The corresponding AddCallUseCase 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 either from the webhook or from the modal window.

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

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

    if ($record = $voipCall->getRecording() ?? '') {
            $record = sprintf(
                '%s/voip/%s/get_call_record/%s',
                $this->appConfig->getBaseUrl(),
                $task->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
    if ($voipCall->getDirection() === CallType::INBOUND) {
        //call our client API to create incoming lead
        $incomingLead = $this->unsortedService->makeUnsorted(
            $call,
            $this->OAuthConfig->getWidgetCode(),
            $voipCall->getResponsibleUserId()
        );
        $incomingLeadUid = $incomingLead->getUid();
        $voipCall->setUnsortedUid($incomingLeadUid)->save();
    }
    $task->setSuccess(true);
}

AddCallWorker grabs a task from the queue with Kommo account ID and call ID and searches for the call in the integration database. If it’s already added - no need to add once again.

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');
    }
}

Call note

Call logging is performed by creating events within the corresponding entity, categorized under specific event types for outgoing and incoming calls. If the VoIP system supports call recording functionality, the user interface may present a URL and an audio player for playback of the recorded call. For proper operation of the audio player, the server hosting the recording must include the HTTP header Accept-Ranges: bytes in its response. Absence of this header may result in impaired seek functionality (e.g., fast-forwarding or rewinding) during playback.

The call logging is performed via the POST endpoint /api/v4/calls. This endpoint automatically searches for entities by the provided phone number and attaches the call record to the appropriate entity based on a defined algorithm. Detailed documentation regarding the call attachment algorithm is available within the method specification. If no entity matching the provided phone number exists in the system database, the call record will not be linked to any entity.

The system provides multiple options for displaying call information, depending on the call direction (incoming or outgoing).


What’s Next

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