Kommo + Kixie: звонки и SMS в карточку сделки

Kixie отправляет данные каждого звонка и SMS через webhook на ваш сервис. Сервис находит контакт в Kommo по номеру телефона, создаёт заметку с записью, транскриптом и длительностью. Нативного виджета Kixie в Kommo нет - только кастомная интеграция.

SDR использует Kixie для обзвона: power dialer, AI-транскрипция, SMS после звонка. Но все эти данные остаются только в Kixie. Когда AE принимает лид, он открывает Kommo и видит пустую карточку - ни истории звонков, ни транскриптов, ни попыток дозвона. Приходится заходить в Kixie отдельно, искать по номеру, копировать. При 200+ звонках в день это становится узким местом: контекст теряется, передача лидов замедляется, менеджеры дублируют работу. Kixie поддерживает webhook-интеграцию с любым CRM через универсальный endpoint - именно это используется для связи с Kommo. В этой статье разберём архитектуру, Python-код и кейс SDR-команды с цифрами.

Почему нативной интеграции Kixie с Kommo нет

Kixie имеет нативные интеграции с HubSpot, Salesforce, Pipedrive и рядом других CRM. Kommo в этом списке отсутствует. Причина стандартная: Kixie приоритизирует US-рынок, где Kommo менее распространён.

Попытка решить через Zapier наталкивается на ограничения: Zapier не обрабатывает поиск контакта в Kommo по номеру телефона в формате E.164, не умеет корректно создавать «note with attachment» с recording_url, и не поддерживает batch-операции при высоком объёме звонков (200+ в день - это 200+ Zap-операций, плюс стоимость).

Кастомный webhook-обработчик решает всё это за фиксированную стоимость инфраструктуры.

E.164 - международный формат номера телефона: +[код страны][номер], например +12025551234. Kommo хранит номера в этом формате, и Kixie передаёт их так же - это важно для правильного поиска контакта.

Архитектура интеграции

Поток данных односторонний: Kixie -> ваш сервис -> Kommo.

При завершении звонка (event: call.completed):

  • Kixie отправляет payload с: номером звонящего, номером принявшего, длительностью, recording_url, transcript (если включён AI)
  • Сервис ищет контакт в Kommo по номеру телефона
  • Если контакт найден - добавляет заметку с деталями звонка и ссылкой на запись
  • Если не найден - создаёт новый контакт и сделку (опционально)

При получении/отправке SMS (events: sms.received, sms.sent):

  • Kixie отправляет payload с номером и текстом сообщения
  • Сервис добавляет SMS как заметку в карточку соответствующего контакта Kommo

Аутентификация Kixie: API Key передаётся как query-параметр или в заголовке Authorization. Webhook-endpoint настраивается в Kixie Dashboard -> Manage -> Webhooks.

import httpx
from fastapi import FastAPI, Request
from typing import Optional
import logging

app = FastAPI()
logger = logging.getLogger(__name__)

KOMMO_SUBDOMAIN = "your_company"
KOMMO_TOKEN = "your_kommo_token"


def normalize_phone(phone: str) -> str:
    """Нормализует номер телефона к E.164 для поиска в Kommo."""
    digits = "".join(filter(str.isdigit, phone))
    if not digits.startswith("1") and len(digits) == 10:
        digits = "1" + digits  # US номер без кода страны
    return "+" + digits


async def find_kommo_contact(phone: str) -> Optional[dict]:
    """Ищет контакт в Kommo по номеру телефона."""
    normalized = normalize_phone(phone)
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4/contacts",
            headers={"Authorization": f"Bearer {KOMMO_TOKEN}"},
            params={"query": normalized},
            timeout=10.0
        )
        if resp.status_code == 204:
            return None  # контакт не найден
        resp.raise_for_status()
        data = resp.json()
        contacts = data.get("_embedded", {}).get("contacts", [])
        return contacts[0] if contacts else None


async def add_kommo_note(contact_id: int, note_text: str, lead_id: Optional[int] = None):
    """Добавляет заметку к контакту или сделке в Kommo."""
    entity_type = "leads" if lead_id else "contacts"
    entity_id = lead_id or contact_id

    async with httpx.AsyncClient() as client:
        await client.post(
            f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4/{entity_type}/{entity_id}/notes",
            headers={"Authorization": f"Bearer {KOMMO_TOKEN}"},
            json=[{
                "note_type": "common",
                "params": {"text": note_text}
            }],
            timeout=10.0
        )


async def get_contact_lead(contact_id: int) -> Optional[int]:
    """Находит активную сделку контакта."""
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4/contacts/{contact_id}",
            headers={"Authorization": f"Bearer {KOMMO_TOKEN}"},
            params={"with": "leads"},
            timeout=10.0
        )
        resp.raise_for_status()
        data = resp.json()
        leads = data.get("_embedded", {}).get("leads", [])
        return leads[0]["id"] if leads else None


@app.post("/kixie/webhook")
async def handle_kixie_webhook(request: Request):
    """Обрабатывает все webhook-события от Kixie."""
    data = await request.json()
    event_type = data.get("event_type", "")

    # Определяем номер телефона для поиска
    if event_type == "call.completed":
        # Для входящих берём звонящего, для исходящих - принимающего
        direction = data.get("direction", "outbound")
        phone = data.get("from_number") if direction == "inbound" else data.get("to_number")
    elif event_type in ("sms.received", "sms.sent"):
        direction = "inbound" if event_type == "sms.received" else "outbound"
        phone = data.get("from_number") if direction == "inbound" else data.get("to_number")
    else:
        return {"status": "skipped", "event": event_type}

    contact = await find_kommo_contact(phone)
    if not contact:
        logger.warning(f"Contact not found for phone: {phone}")
        return {"status": "contact_not_found"}

    contact_id = contact["id"]
    lead_id = await get_contact_lead(contact_id)

    # Формируем текст заметки
    if event_type == "call.completed":
        duration_sec = data.get("duration", 0)
        duration_fmt = f"{duration_sec // 60}:{duration_sec % 60:02d}"
        recording_url = data.get("recording_url", "")
        transcript = data.get("transcript", "")
        disposition = data.get("disposition", "")

        note_parts = [
            f"Kixie звонок [{direction}] - {duration_fmt}",
            f"Статус: {disposition}" if disposition else "",
            f"Запись: {recording_url}" if recording_url else "",
            f"Транскрипт:\n{transcript[:2000]}" if transcript else ""  # ограничиваем длину
        ]
        note_text = "\n".join(p for p in note_parts if p)

    elif event_type in ("sms.received", "sms.sent"):
        sms_body = data.get("body", "")
        arrow = "<-" if event_type == "sms.received" else "->"
        note_text = f"Kixie SMS [{direction} {arrow}]:\n{sms_body}"

    await add_kommo_note(contact_id, note_text, lead_id)
    return {"status": "ok", "contact_id": contact_id}

Пошаговая реализация

Шаг 1. Настройка webhook в Kixie

В Kixie Dashboard -> Manage -> Webhooks создайте новый webhook. Укажите URL вашего сервиса (https://your-service.com/kixie/webhook). Выберите события: End Call, SMS Inbound, SMS Outbound. Если у вас включён AI-транскрипт (Kixie CI), также добавьте CI Summary webhook.

Альтернативно: настройте через API командой POST https://apig.kixie.com/app/v1/api/postwebhook.

Шаг 2. Нормализация номеров телефонов

Kixie может передавать номера в разных форматах в зависимости от настроек. Всегда нормализуйте к E.164 перед поиском в Kommo. Kommo API поддерживает поиск по частичному совпадению через параметр query, но E.164 даёт наиболее точный результат.

Шаг 3. Логика поиска и создания контактов

Если контакт не найден, решите заранее: создавать новый контакт и сделку автоматически или только логировать. Для SDR-команд с входящими звонками рекомендуем автосоздание. Для исходящего обзвона по базе - обычно контакты уже есть в CRM.

Шаг 4. AI-транскрипт и CI Summary

Kixie CI (Conversation Intelligence) отправляет дополнительный webhook ci_summary через 2-5 минут после звонка - после завершения обработки AI. Настройте отдельный обработчик или используйте тот же endpoint с разными event_type. Транскрипт добавляйте как отдельную заметку с пометкой «AI Summary».

Шаг 5. Обработка autodial-сессий

При использовании Power Dialer Kixie генерирует последовательность событий для каждой попытки. Фильтруйте по disposition: логируйте только connected звонки в основную заметку, остальные попытки (busy, no_answer, voicemail) - сокращённой записью. Это предотвращает засорение карточки.

Шаг 6. Rate limits Kommo API

Kommo ограничивает количество запросов: 7 req/sec для большинства endpoint. При 200+ звонках в день с одновременным autodial возможны пики. Используйте очередь (asyncio.Queue или Redis + Celery) и exponential backoff при получении 429.

Реальный кейс: SDR-команда, 250 звонков в день

Клиент - B2B SaaS-компания с SDR-командой из 8 человек в Европе. Используют Kixie Power Dialer для outbound-обзвона базы. Kommo - основная CRM, где AE ведут сделки.

До интеграции: SDR после каждого звонка вручную открывал Kommo, находил карточку, писал заметку. Или (чаще) - не делал этого, потому что некогда. AE принимал лид и не знал истории звонков.

После интеграции: каждый звонок длиннее 30 секунд автоматически появляется в Kommo как заметка с: продолжительностью, статусом (connected / voicemail / no_answer), ссылкой на запись, AI-транскриптом (если разговор состоялся).

Результат за первый месяц:

  • 100% покрытие: все звонки фиксируются в CRM без исключений
  • SDR сэкономили ~45 минут в день каждый (8 человек x 45 мин = 6 часов/день)
  • AE при получении лида видят полный контекст переговоров
  • Менеджер по продажам получил возможность анализировать среднюю длительность звонков и конверсию по dispositions прямо в Kommo-отчётах

Полезный побочный эффект: записи звонков стали доступны в карточках сделок для онбординга новых SDR - они слушают реальные примеры прямо в CRM.

Для кого подходит эта интеграция

  • SDR-команды с Kixie Power Dialer, которые используют Kommo как CRM
  • Компании с входящим call-центром на Kixie, где контакты ведутся в Kommo
  • Команды, которые хотят AI-транскрипты звонков в карточке CRM без ручного копирования
  • Случаи, когда SDR и AE работают в разных инструментах и нужна прозрачная передача контекста

Если вы рассматриваете альтернативы для телефонии, посмотрите сравнение с похожими интеграциями: Kommo + Aircall и Kommo + JustCall - там аналогичная архитектура, но другие особенности API.

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

Kixie передаёт транскрипты всех звонков или только длинных?

AI-транскрипция в Kixie (функция CI - Conversation Intelligence) запускается для звонков длиннее определённого порога - обычно 30 секунд. Короткие звонки и voicemail транскрибируются опционально. Транскрипт приходит в отдельном webhook ci_summary через 2-5 минут после звонка, не сразу в call.completed.

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

Kixie гарантирует доставку webhook как минимум один раз (at-least-once delivery), то есть дубли возможны. Используйте идемпотентный ключ: сохраняйте call_id из payload в Redis с TTL 24 часа. Перед созданием заметки проверяйте, обрабатывался ли уже этот call_id.

Что если контакта в Kommo нет, а SDR звонит по новому номеру?

Есть два варианта. Первый: создавать новый контакт автоматически с номером телефона и именем из Kixie payload - подходит для входящих звонков. Второй: только логировать незнакомые номера во внешний лог без создания контакта в Kommo - подходит для outbound по новой базе, которую SDR будет сначала квалифицировать. Выбор зависит от вашего процесса.

Kixie поддерживает SMS в нескольких странах?

Да. Kixie поддерживает SMS в US, Canada, UK и ряде других стран через собственную телефонную инфраструктуру. Для каждого рынка требуется зарегистрированный номер в Kixie. Webhook-payload содержит country_code, что позволяет маршрутизировать события по разным воронкам Kommo если у вас мультирегиональная структура.

Можно ли ограничить запись в Kommo только для определённых диспозиций?

Да, это рекомендуемая практика. В коде webhook-обработчика добавьте фильтр по disposition: пишите полную заметку только для connected, для voicemail - сокращённую, для busy и no_answer - вообще не создавайте заметку или пишите только инкремент счётчика попыток. Это поддерживает карточки Kommo в чистоте.


Если ваши SDR используют Kixie, а история звонков не попадает в Kommo - опишите задачу команде Exceltic.dev. Это типовая задача: обсудим объём звонков, структуру воронки и предложим решение.

Ещё статьи

Все →