Call from Webhook

As we mentioned in call logging, three events could happen when a call ends

  • A webhook about ending the call could 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.
  • The manager adds the call result, as a note without changing the suggested entity.
  • The VoIP service sends the call recording, and it will be linked to the call card.

As we previously mentioned these events aren’t synchronized nor mandatory. Now we are discussing what should be done once we receive a webhook about ending a call. We should register all information about the call, but we don’t want to create a new call if it’s already created.

So we will check first, if it doesn’t exist we will implement the call logging use case to create a new call. If it does, we will update the existing call note.

In the call logging use case, we created a class to grab all calls from the VoIP service and store them in the calls repository in the integration database.

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

For each call, maybe a task to save the call event or a task to update an existing call is created. The constructor of the task is shown below.

//SaveCallEventTask
public function __construct(
	private int $kommoAccountId,
	private string $toPhone,
	private string $fromPhone,
	private string $callId,
	private CallType $direction,
	private int $status,
	private int $duration,
	private int $startedAt,
	private int $userId,
	private string|null $recording = null
) ;
//UpdateCallTask
public function __construct(
	protected int $KommoAccountId,
	protected string $callId,
);

Two use cases are declared, one to save the call event and the other to update an already saved call, depending on the situation. They take the corresponding task as input, and send the task to the corresponding queue as well.

//SaveCallEventUseCase
public function handle(SaveCallEventTask $task): void
{
	$voipCall = voipCallsTable::getByCallIdAndkommoAccountId(
		$task->getCallId(),
		$task->getkommoAccountId()
	);
	$isNew = $voipCall === null;
	$voipCall = $isNew ? voipCalls::create() : $voipCall;

	$responsibleUser = $voipCall->getResponsibleUserId() ?? task->$getUserId();
	$voipCall
		->setDirection($task->getDirection())
		->setCallId($task->getCallId())
		->setkommoAccountId($task->getkommoAccountId())
		->setToNumber($task->getToPhone())
		->setFromNumber($task->getFromPhone())
		->setDuration($task->getDuration())
		->setStartedAt($task->getStartedAt())
		->setResponsibleUserId($responsibleUser)
		->setRecording($task->getRecording());
	$voipCall->getStatus() ?: $voipCall->setStatus($task->getStatus());
	$voipCall->save();
	
	//We will try to log the call with basic information 
	//by calling the AddCallWorker from 
	//the call logging use case AddCallUseCase 
	$data = [
		'call_id' => $task->getCallId(),
		'account_id' => $task->getkommoAccountId(),
	];
	$queueName = $isNew ? AddCallWorker::QUEUE_NAME : UpdateCallNoteWorker::QUEUE_NAME;
	//$queueName = AddCallWorker::QUEUE_NAME;
	$queueTask = new QueueTask($queueName, $data);
	$this->queue->send($queueTask);
	$task->setSuccess(true);
}

//UpdateCallUseCase
public function handle(UpdateCallTask $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);

	//Call our client API to create call event
	$apiClient = $this->KommoApiClient;
	$apiClient->updateCallEvent(
                $call,
                $voipCall->getResponsibleUserId() ?? self::BOT_USER_ID,
                $voipCall->getEntityId(),
                $voipCall->getEntityType(),
                $voipCall->getParentId()
            );	
	$voipCall->setDelivered(DeliveryStatus::COMPLETED);
	$voipCall->save();
	$task->setSuccess(true);
}

Now let’s define the two workers

//ProcessCallWebhookWorker
public function run(array $data, LoggerInterface $logger): void
{
	$taskData = $data['data'];
	$webhookData = FromWebhook::fromArray($data['data']['webhook_data']);
	$call = new Call();
	$call = [
		'CallId' => $webhookData->getCallId() ?? (string)$webhookData->getSessionId(),
		'ToNumber' => $webhookData->getToPhoneNumber(),
		'FromNumber' => $webhookData->getFromPhoneNumber(),
		'CallType' => $webhookData->getDirection(),
		'Duration' => 0,
		'CallResult' => 'done',
		'RecordLink' => null,
		'StartedAt' => $webhookData->getStartTime()->toIso8601ZuluString()
	];
	if($call['CallType']==INBOUND)
	{ 
		$call['ResponsibleUser'] = VoIPUsers::getKommoUserId($call['ToNumber']); 
	}else { 
		$call['ResponsibleUser'] = VoIPUsers::getKommoUserId($call['FromNumber']); 
	} 
	$task = new SaveCallEventTask( 
		(int)$taskData['kommo_account_id'], 
		$call['ToNumber'], 
		$call['FromNumber'], 
		$call['CallId'], 
		$call['CallType'], 
		4, 
		$call['Duration'], 
		$call['StartedAt'], 
		$call['ResponsibleUser'], 
		null 
	); 
	$this->saveCallEventUseCase->handle($task); 
	if (!$task->isSuccess()) 
	{ 
		throw BusinessLogicException::create('Save call event error'); 
	} 
} 

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

When saving the call event, we either added the call as in call logging use case, or updated an existing call. We should also handle the receiving of the webhook and submit the task to the queue.

//ProcessWebhookHandler
public function handle(ServerRequestInterface $request): ResponseInterface
{
	$data = json_decode($request, true) ?? '';
	if ($data) {
		$callWebhook = FromWebhook::fromArray($data);
		$queueTask = new QueueTask(
			ProcessCallWebhookWorker::QUEUE_NAME,
			[
				'kommo_account_id' => (int)$data['kommo_account_id'],
				'webhook_data' => $callWebhook->toArray(),
			]
		);
		$this->queue->send($queueTask);
	}
}

Now let’s discuss what happens when the manager chooses a different entity and fills the call result.