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. Это типовая задача: обсудим объём звонков, структуру воронки и предложим решение.