Kommo + RingCentral: записи разговоров и итоги звонков в карточку сделки

Kommo + RingCentral: записи разговоров и итоги звонков в карточку сделки

RingCentral имеет нативную интеграцию с Kommo: click-to-call, автоматическое создание лида при входящем звонке от незнакомого номера, базовый лог звонков в таймлайне сделки. Но нативная интеграция фиксирует только сам факт звонка — продолжительность и номер. Ни записи разговора, ни итога звонка, ни транскрипта в карточке нет. Это закрывается через RingCentral REST API.

Что упускает нативная интеграция Kommo + RingCentral

Стандартная интеграция RingCentral через Kommo Marketplace даёт:
— Click-to-call из карточки контакта
— Входящий звонок -> уведомление в Kommo, создание лида если контакт новый
— Запись факта звонка (кто позвонил, длительность) в таймлайн сделки

Чего нет:
Запись разговора не прикрепляется к сделке — она остаётся только в RingCentral
Итог звонка (статус, комментарий менеджера) не попадает в кастомные поля
Транскрипт недоступен в CRM даже если RingCentral AI его создал
Логика маршрутизации — кому назначить сделку после звонка — не настраивается

Для команд с 20+ звонками в день это означает, что Sales Director не видит контекст разговоров в Kommo без перехода в RingCentral. Customer Success не знает, о чём говорили с клиентом до передачи. При смене менеджера история теряется.

Архитектура расширенной интеграции

RingCentral Webhook: telephony/sessions -> сессия завершена (Disconnected)
  ↓ Backend
  1. Получить session_id, extension_id, from/to номера
  2. Подождать 60 сек (запись обрабатывается асинхронно)
  3. GET /account/~/call-log?sessionId={session_id}&view=Detailed
     -> duration, result, recording.id
  4. GET /account/~/recording/{recording_id}/content
     -> скачать аудио MP3
  5. Загрузить MP3 в хранилище (S3/R2)
     -> получить публичный URL
  6. Kommo: найти сделку по номеру телефона (контакт -> сделки)
  7. Kommo: POST /leads/{deal_id}/notes
     -> Note с типом «звонок»: длительность, ссылка на запись
  8. Kommo: PATCH /leads/{deal_id}
     -> кастомное поле last_call_result = 'Completed' / 'No Answer' / 'Busy'
     -> last_call_date = timestamp

RingCentral REST API: ключевые запросы

Base URL: https://platform.ringcentral.com/restapi/v1.0/. Аутентификация: JWT flow для server-to-server (создать JWT credential в Developer Console -> обменять на access_token).

Получение access token через JWT:

import requests

RC_CLIENT_ID = 'your_client_id'
RC_CLIENT_SECRET = 'your_client_secret'
RC_JWT = 'your_jwt_credential'

def get_access_token() -> str:
    resp = requests.post(
        'https://platform.ringcentral.com/restapi/oauth/token',
        auth=(RC_CLIENT_ID, RC_CLIENT_SECRET),
        data={
            'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion': RC_JWT
        }
    )
    resp.raise_for_status()
    return resp.json()['access_token']

Получение деталей звонка:

def get_call_details(session_id: str, token: str) -> dict:
    headers = {'Authorization': f'Bearer {token}'}
    resp = requests.get(
        'https://platform.ringcentral.com/restapi/v1.0/account/~/call-log',
        headers=headers,
        params={
            'sessionId': session_id,
            'view': 'Detailed',
            'dateFrom': '2024-01-01T00:00:00Z'  # или минус 1 час
        }
    )
    resp.raise_for_status()
    records = resp.json().get('records', [])
    if not records:
        return {}

    record = records[0]
    return {
        'duration': record.get('duration'),
        'result': record.get('result'),  # 'Accepted', 'No Answer', 'Busy', etc.
        'from': record.get('from', {}).get('phoneNumber'),
        'to': record.get('to', {}).get('phoneNumber'),
        'recording_id': record.get('recording', {}).get('id')
    }

Скачать запись разговора:

def download_recording(recording_id: str, token: str) -> bytes:
    headers = {'Authorization': f'Bearer {token}'}
    resp = requests.get(
        f'https://platform.ringcentral.com/restapi/v1.0'
        f'/account/~/recording/{recording_id}/content',
        headers=headers
    )
    resp.raise_for_status()
    return resp.content  # MP3 bytes

Обработка telephony/sessions webhook:

from flask import Flask, request
import time

app = Flask(__name__)

@app.route('/webhooks/ringcentral', methods=['POST'])
def ringcentral_webhook():
    # RingCentral требует валидацию при регистрации webhook
    validation_token = request.headers.get('Validation-Token')
    if validation_token:
        return validation_token, 200, {'Validation-Token': validation_token}

    payload = request.json
    event = payload.get('event', '')

    # Обрабатываем только завершённые сессии
    if 'telephony/sessions' not in event:
        return '', 200

    body = payload.get('body', {})
    session_id = body.get('sessionId')
    parties = body.get('parties', [])

    # Проверяем что хотя бы одна сторона в статусе Disconnected
    has_disconnected = any(
        p.get('status', {}).get('code') == 'Disconnected'
        for p in parties
    )
    if not has_disconnected:
        return '', 200

    # Очередь для асинхронной обработки (запись появляется с задержкой)
    enqueue_call_processing(session_id, delay_seconds=60)
    return '', 200

def process_completed_call(session_id: str):
    token = get_access_token()
    details = get_call_details(session_id, token)
    if not details:
        return

    from_number = details['from']
    result = details['result']
    duration = details['duration']
    recording_id = details.get('recording_id')

    # Найти сделку в Kommo по номеру телефона
    deal_id = find_kommo_deal_by_phone(from_number)
    if not deal_id:
        return

    # Скачать и сохранить запись
    recording_url = None
    if recording_id:
        audio = download_recording(recording_id, token)
        recording_url = upload_to_storage(audio, f'{session_id}.mp3')

    # Note в Kommo
    note_text = f'RingCentral звонок: {duration} сек, результат: {result}'
    if recording_url:
        note_text += f'\nЗапись: {recording_url}'
    create_kommo_note(deal_id, note_text)

    # Обновить кастомные поля
    update_kommo_deal(deal_id, {
        'last_call_result': result,
        'last_call_duration': duration
    })

Регистрация webhook в RingCentral:

def register_webhook(webhook_url: str, token: str):
    resp = requests.post(
        'https://platform.ringcentral.com/restapi/v1.0/subscription',
        headers={'Authorization': f'Bearer {token}'},
        json={
            'eventFilters': [
                '/restapi/v1.0/account/~/telephony/sessions'
            ],
            'deliveryMode': {
                'transportType': 'WebHook',
                'address': webhook_url
            },
            'expiresIn': 604800  # 7 дней, нужно обновлять
        }
    )
    return resp.json()

Важно: Подписка на RingCentral webhook истекает (по умолчанию 7 дней). Необходимо обновлять её по расписанию через PUT /subscription/{subscriptionId} или подписаться на событие /restapi/v1.0/subscription/~?threshold=60&interval=30 для автоматического обновления.

Интеграция с RingCentral AI — транскрипты

RingCentral RingSense AI создаёт транскрипты и саммари звонков на тарифах с AI-функциями. Они доступны через GET /account/~/call-log/{callId}?view=Detailed — поле aiTranscriptUrl содержит ссылку на транскрипт.

def get_call_transcript(call_id: str, token: str) -> str:
    headers = {'Authorization': f'Bearer {token}'}
    resp = requests.get(
        f'https://platform.ringcentral.com/restapi/v1.0'
        f'/account/~/call-log/{call_id}',
        headers=headers,
        params={'view': 'Detailed'}
    )
    data = resp.json()
    transcript_url = data.get('aiTranscriptUrl')
    if not transcript_url:
        return ''
    tr = requests.get(transcript_url, headers=headers)
    return tr.text

Транскрипт сохраняется как отдельный Note в Kommo с тегом «Транскрипт RingCentral» — отдельно от Note со ссылкой на запись. Аналогичный подход используется при интеграции Kommo с Fireflies.ai.

Маршрутизация сделок после звонка

Дополнительная возможность: автоматически переназначать сделку или менять этап воронки в зависимости от исхода звонка.

CALL_RESULT_ACTIONS = {
    'Accepted': None,              # звонок принят - ничего не меняем
    'No Answer': 'schedule_followup',  # перезвонить через час
    'Busy': 'schedule_followup',
    'Voicemail': 'send_followup_email'
}

def handle_call_outcome(deal_id: int, result: str):
    action = CALL_RESULT_ACTIONS.get(result)
    if action == 'schedule_followup':
        create_kommo_task(
            deal_id,
            f'Перезвонить - клиент не ответил ({result})',
            due_in_minutes=60
        )

Реальный кейс

B2B-компания (EU, SaaS, 8 продавцов, 80–100 исходящих звонков в день через RingCentral + Kommo):

  • До: записи звонков существовали только в RingCentral. Менеджер после звонка вручную вносил итог в Kommo. 30–40% звонков не логировались — менеджеры «забывали». Sales Director слушал записи в RingCentral, не имея контекста CRM рядом.
  • После: каждый завершённый звонок -> Note в Kommo с записью и результатом за 90 секунд. Итог «No Answer» -> автоматическая задача на перезвон через час. Sales Director ревьюит звонки прямо из карточки сделки.
  • Дополнительно: при передаче сделки новому менеджеру тот сразу видит в таймлайне записи последних 5 звонков — контекст не теряется.

Для кого актуально

  • Команды, использующие RingCentral как корпоративную телефонию и Kommo как CRM
  • 20+ звонков в день — ручное логирование становится нерентабельным
  • Требуется история разговоров в карточке сделки: для передачи клиента, онбординга, Customer Success
  • Нативная интеграция установлена, но записей в CRM нет

Часто задаваемые вопросы

Чем JWT-авторизация RingCentral отличается от OAuth?

JWT (RFC 7523) — server-to-server поток без участия пользователя. JWT credential создаётся в Developer Console и позволяет получить access_token для доступа к данным аккаунта. OAuth Authorization Code используется когда пользователь должен сам авторизовать доступ (multi-tenant SaaS). Для серверной интеграции с Kommo — JWT.

Какие разрешения нужны для доступа к записям?

Два API permissions обязательны: ReadCallLog для доступа к журналу звонков и ReadCallRecording для скачивания аудио. Задаются в настройках приложения в RingCentral Developer Console. Без них /recording/{id}/content вернёт 403.

Запись появляется сразу после окончания звонка?

Нет. RingCentral обрабатывает запись асинхронно — обычно 30–90 секунд. Если запросить call-log сразу после события Disconnected, поле recording.id будет пустым. Рекомендуется задержка 60 секунд или polling с проверкой раз в 30 секунд.

Нужен ли Premium-тариф RingCentral для API?

Доступ к REST API доступен на Premium и Ultimate тарифах. На Standard тарифе API ограничен. Запись разговоров также требует Premium+. Перед внедрением проверьте текущий тариф: Settings -> Billing в RingCentral Admin Portal.

Как связать звонок с нужной сделкой если у контакта несколько открытых сделок?

Логика: искать активную сделку в статусах кроме Won/Lost. Если несколько — выбирать последнюю по дате обновления. Или добавить кастомное поле «основной номер ответственного» в сделку и матчить по нему.

Итого

  • Нативная интеграция Kommo + RingCentral: click-to-call и базовый лог. Записей и транскриптов в CRM нет
  • RingCentral REST API: JWT auth, event filter /account/~/telephony/sessions
  • После Disconnected: call-log -> recording -> upload -> Note в Kommo + кастомные поля
  • Подписка webhook истекает через 7 дней — нужно автообновление
  • Задержка 60 сек перед запросом recording (асинхронная обработка)
  • Типовой срок разработки — 1–2 недели

Если у вас RingCentral и Kommo и записи звонков не появляются в карточках — опишите текущую конфигурацию. Exceltic.dev настроит кастомную интеграцию IP-телефонии с полным логированием разговоров.

Ещё статьи

Все →