Call result

Call result card

After the conversation ends, managers can be given the option to provide a call result. The Call result modal window displays information about the caller and includes options to select the entity (lead, contact or company) to associate the call with, attach the call recording, add a task with a specific time, and leave notes about the conversation if any. If the manager does not answer the call, a record with zero duration can be saved. The appearance of the call result window is handled through the JS part of the widget, while the actual call update is performed via the integration backend.

An example of the call result window:

The modal window might consist of the following elements:

  • Window name: Call summary
  • Linked entity (lead/contact/company)
  • Lead title is a field with search property to find the suitable lead/customer
  • Call recording panel (indicating call duration and a play button)
  • Time and note for a task
  • Save or Cancel buttons

Template for rendering your Call result modal window:

import Modal from "lib/components/base/modal";
import markup from "./markup.js";

const appendCallResultModal = () => {
  /**
   * You can use the Modal class to open a modal window.
   */
  const modal = new Modal({
    class_name: "modal-window",
    init: function ($modal_body) {
      $modal_body
        /**
         * Triggers the display of the modal window.
         */
        .trigger("modal:loaded")
        /**
         * Displays your markup.
         */
        .html(markup)
        /**
         * Configures the modal window.
         */
        .trigger("modal:centrify");
    },
    destroy: () => {},
  });

  /**
   * Here goes the logic for your modal window.
   */
};

After choosing the entity and filling the fields, you should save the call result, update the call by changing its link to the chosen entity if it’s done. For that, a task is created to be added to the queue.

Let’s consider the following sample scenario:

  1. A call from an existing contact is performed.
  2. Webhook about ending the call is launched from the VoIP service. Then the integration backend needs to find an appropriate entity to link the call or create an incoming lead if no entity connected to the phone number exists in your Kommo account.
  3. The manager fills in the Call result modal and selects a different entity than the one suggested by the algorithm. The note should be linked to the selected entity, and the link to the previously suggested entity should be removed by the integration.
  4. The VoIP integration backend then sends a Call note request to get it linked to the selected contact card.

As mentioned in the Call logging article, all calls should be retrieved from the VoIP service and stored in the calls repository within the integration database. We've also implemented a function to retrieve a call based on its identifiers:

public static function getByCallIdAndKommoAccountId(string $callId, int $kommoAccountId): VoipCalls {
    return VoipCalls::query() 
        ->where('call_id', '=', $callId)
        ->where('kommo_account_id', '=', $kommoAccountId)
        ->first();
}

A task to update the call according to the information that came from the modal call result window:

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

An example about the use case which is responsible for updating the call according to the information that came from the call result modal window is explained here:

public function handle(UpdateFromModalTask $task): void
    {
        $widgetSettings = WidgetSettingsRepository::getByKommoAccountId($task->getKommoAccountId());
        $voipCall = VoipCalls::getByCallIdAndKommoAccountId(
            $task->getCallId(),
            $widgetSettings->getKommoAccountId()
        );
        $call = Call::fromModel($voipCall);
        if ($record = $voipCall->getRecording() ?? '') {
            $record = sprintf(
                '%s/voip/%s/get_call_record/%s',
                $this->appConfig->getBaseUrl(),
                $widgetSettings->getKommoAccountId(),
                $voipCall->getCallId()
            );
        }
        $call->setRecordLink($record);
        $unsorted = null;
        /** If the call is in incoming leads:
        * a) If the manager choose Lead entity, you need to attach a Contact to it;
        * b) If the manager choose Contact/Company entity, you need to pick it up from the contact in the Incoming lead
        */
        if ($voipCall->getUnsortedUid() !== null) {
                $unsorted = $this->unsortedService
                ->findUnsortedByUid($voipCall->getUnsortedUid());
                $unsortedContact = $unsorted->getContacts()?->first();

                $entityType = KommoEntityType::tryFrom($task->getEntityType());
                switch ($entityType) {
                    case KommoEntityType::LEADS:
                        if ($unsortedContact !== null) {
                            $this->contactService
                            ->linkContact($unsortedContact, $entityType, $task->getEntityId());
                        }
                        break;
                    case KommoEntityType::CONTACTS:
                    case KommoEntityType::COMPANIES:
                        // in the incoming lead only the contact id. We'll get all the information.
                        if ($unsortedContact !== null) {
                            $unsortedContact = $this
                            ->contactService>getContactById($unsortedContact->getId());
                            $phones = $unsortedContact
                            ->getCustomFieldsValues()
                            ?->getBy('fieldCode', 'PHONE');
                            $newPhone = $phones?->getValues()?->first()->getValue();
                            if (!empty($newPhone)) {
                                $this->contactService->updatePhones($task->getEntityId(),                                           $entityType, (string)$newPhone);
                            }
                        }
                        break;
                    default:
                        throw UnsupportedEntityException::create();
                }
        }
        $this->contactService->updateCallEvent(
            $call,
            $voipCall->getResponsibleUserId() ?? self::BOT_USER_ID,
            $voipCall->getEntityId(),
            $voipCall->getEntityType(),
            $task->getEntityId(),
            $task->getEntityType()
        );

        // reject incoming lead after declining the call note
        if ($unsorted) {
            $this->unsortedService->declineUnsorted($unsorted);
            $voipCall->setUnsortedUid(null);
        }
        $voipCall->getEntityType())->save();
        $task->setSuccess($success);
}

The task is created inside the worker, which grabs the task from the queue and sends it to the handler:

public function run(array $data, LoggerInterface $logger): void 
{
    $taskData = $data['data'] ?? [];
    $task = new UpdateFromModalTask(
        $taskData['account_id'],
        $taskData['call_id'],
        $taskData['entity_id'],
        $taskData['entity_type']
    );
    $this->updateFromModalUseCase->handle($task);
    if (!$task->isSuccess()) {
        throw BusinessLogicException::create('Update 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.

A Call note can be added via the POST endpoint /api/v4/calls. The 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 Caller ID use case.