Kommo + Whereby: видеозвонки из воронки без перехода в другой сервис

Whereby Embedded позволяет создавать комнаты для видеозвонков через API за один HTTP-запрос. Для Kommo это решает конкретную задачу: когда менеджер переводит сделку на этап “Демо” или “Онбординг”, комната создаётся автоматически, ссылка добавляется в note к сделке - и можно сразу отправить клиенту. Никакого перехода в Whereby, никакого ручного копирования ссылки.

Whereby Embedded API использует Bearer token (API Key из Whereby Embedded аккаунта). Один эндпоинт для создания комнаты: POST /v1/meetings. В ответе - roomUrl для гостей и hostRoomUrl с правами хоста. Минимальный запрос - 3 поля.

Whereby Embedded - версия Whereby для разработчиков с API для создания комнат программно и возможностью встройки видео в собственный интерфейс. Отличается от обычного Whereby: Embedded требует платного плана и API Key.

Почему стандартный подход не работает

Стандартный Zapier-коннектор Whereby создаёт “постоянные” комнаты без привязки к конкретному временному слоту и без hostRoomUrl. В результате менеджер получает ссылку на комнату, которая либо уже занята другим звонком, либо доступна без его ведома.

Более глубокая проблема: Zapier-комнаты не удаляются автоматически. Если сделка отменяется, комната остаётся открытой. За месяц накапливается 20-30 брошенных комнат.

Архитектура

Kommo: сделка -> этап "Демо" или "Онбординг"
  -> Kommo webhook leads.status.changed
  -> Ваш сервер

Ваш сервер:
  -> POST /v1/meetings (endDate, roomNamePrefix)
  -> Response: {roomUrl, meetingId, hostRoomUrl}
  -> Kommo: note с roomUrl (для клиента) и hostRoomUrl (для менеджера)

Реализация

import requests, os
from datetime import datetime, timedelta, timezone
from flask import Flask, request, jsonify

app = Flask(__name__)

WHEREBY_API_KEY  = os.environ["WHEREBY_API_KEY"]
WHEREBY_BASE     = "https://api.whereby.dev/v1"
WHEREBY_HDR      = {"Authorization": f"Bearer {WHEREBY_API_KEY}",
                    "Content-Type": "application/json"}

KOMMO_SUBDOMAIN  = os.environ["KOMMO_SUBDOMAIN"]
KOMMO_TOKEN      = os.environ["KOMMO_ACCESS_TOKEN"]
DEMO_STAGE_ID    = int(os.environ["KOMMO_DEMO_STAGE_ID"])

KOMMO_BASE = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4"
KOMMO_HDR  = {"Authorization": f"Bearer {KOMMO_TOKEN}",
              "Content-Type": "application/json"}

MEETING_DURATION_HOURS = 2  # комната живёт 2 часа после создания

def create_whereby_room(lead_id: int) -> dict:
    end_date = (
        datetime.now(timezone.utc) + timedelta(hours=MEETING_DURATION_HOURS)
    ).strftime("%Y-%m-%dT%H:%M:%S.000Z")

    r = requests.post(
        f"{WHEREBY_BASE}/meetings",
        headers=WHEREBY_HDR,
        json={
            "endDate":        end_date,
            "fields":         ["hostRoomUrl"],
            "roomNamePrefix": f"kommo-{lead_id}-",
            "roomMode":       "normal",
        },
    )
    r.raise_for_status()
    return r.json()

def delete_whereby_room(meeting_id: str):
    requests.delete(
        f"{WHEREBY_BASE}/meetings/{meeting_id}",
        headers=WHEREBY_HDR,
    )

def add_note(lead_id: int, text: str):
    requests.post(
        f"{KOMMO_BASE}/notes",
        headers=KOMMO_HDR,
        json=[{"entity_id": lead_id, "entity_type": "leads",
               "note_type": "common", "params": {"text": text}}],
    )

def save_meeting_id(lead_id: int, meeting_id: str):
    # Сохраняем meetingId в кастомное поле Kommo для последующего удаления
    cf_meeting_id = int(os.environ.get("CF_WHEREBY_MEETING_ID", "0"))
    if cf_meeting_id:
        requests.patch(
            f"{KOMMO_BASE}/leads/{lead_id}",
            headers=KOMMO_HDR,
            json={"custom_fields_values": [
                {"field_id": cf_meeting_id,
                 "values": [{"value": meeting_id}]}
            ]},
        )

@app.route("/webhooks/kommo", methods=["POST"])
def kommo_webhook():
    data = request.json or {}

    # Создание комнаты при переходе на этап Демо
    for lead_data in data.get("leads", {}).get("status", []):
        lead_id    = lead_data.get("id")
        new_status = lead_data.get("status_id")
        if new_status != DEMO_STAGE_ID:
            continue

        meeting    = create_whereby_room(lead_id)
        room_url   = meeting.get("roomUrl", "")
        host_url   = meeting.get("hostRoomUrl", "")
        meeting_id = meeting.get("meetingId", "")

        save_meeting_id(lead_id, meeting_id)
        add_note(
            lead_id,
            f"Whereby комната создана:\n"
            f"Ссылка для клиента: {room_url}\n"
            f"Ссылка для менеджера (хост): {host_url}\n"
            f"Meeting ID: {meeting_id}"
        )

    return jsonify({"status": "ok"}), 200

Удаление комнат при закрытии сделки

Whereby не удаляет комнаты автоматически после endDate - они переходят в неактивное состояние, но занимают лимит. Правильный подход: удалять при закрытии или проигрыше сделки.

def get_cf_value(lead: dict, field_id: int) -> str:
    for cf in lead.get("custom_fields_values", []) or []:
        if cf.get("field_id") == field_id:
            vals = cf.get("values", [])
            return vals[0].get("value", "") if vals else ""
    return ""

@app.route("/webhooks/kommo/closed", methods=["POST"])
def kommo_closed():
    data = request.json or {}
    cf_meeting_id = int(os.environ.get("CF_WHEREBY_MEETING_ID", "0"))
    if not cf_meeting_id:
        return jsonify({"status": "no_cf_configured"}), 200

    for lead_data in data.get("leads", {}).get("delete", []):
        lead_id = lead_data.get("id")
        # Загружаем полные данные сделки чтобы получить кастомное поле
        r = requests.get(
            f"{KOMMO_BASE}/leads/{lead_id}",
            headers=KOMMO_HDR,
            params={"with": "custom_fields_values"},
        )
        lead       = r.json()
        meeting_id = get_cf_value(lead, cf_meeting_id)
        if meeting_id:
            delete_whereby_room(meeting_id)

    return jsonify({"status": "ok"}), 200

Настройка Whereby Embedded

  1. Зарегистрируйтесь на whereby.com/embedded (нужен платный план)
  2. Создайте организацию, перейдите в Configure -> API Keys
  3. Сгенерируйте API Key - это Bearer token для всех запросов
  4. Настройте roomNamePrefix и roomMode под ваши нужды

Режимы комнаты: normal (стандартная) и group (до 100 участников, требует Enterprise план).

Параметры комнаты

# Расширенный запрос с дополнительными параметрами
meeting = requests.post(
    f"{WHEREBY_BASE}/meetings",
    headers=WHEREBY_HDR,
    json={
        "endDate":           "2026-07-01T12:00:00.000Z",
        "fields":            ["hostRoomUrl", "viewerRoomUrl"],
        "roomNamePrefix":    "acme-demo-",
        "roomMode":          "normal",
        "recording":         {"type": "none"},  # или "cloud"
        "chat":              {"enabled": True},
        "screenshare":       {"enabled": True},
    },
)

Поле viewerRoomUrl - ссылка для наблюдателя без прав говорить (полезно для записи презентаций).

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

B2B компании с sales-процессом, который включает демо-звонки или онбординг-сессии. Особенно актуально для SaaS и профессиональных услуг, где демо - обязательный этап воронки. Whereby Embedded популярен в EU именно потому, что данные не уходят на серверы в США - важно для GDPR-compliant компаний.

Другие коммуникационные интеграции: Kommo + Telnyx (SMS/звонки через CPaaS), Kommo + Sinch (SMS EU).

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

Можно ли получить запись звонка через Whereby API?

Да, если включена запись в настройках комнаты ("recording": {"type": "cloud"}). После завершения встречи запись доступна через GET /v1/recordings - список записей с downloadUrl. Записи хранятся на серверах Whereby (EU-регион доступен на Enterprise-плане).

Как ограничить доступ к комнате только для приглашённых?

Whereby Embedded поддерживает roomLock: true при создании комнаты - участники должны ждать допуска от хоста. Хост управляет через hostRoomUrl. Дополнительно можно добавить knock: {"enabled": true} для обязательного стука перед входом.

Поддерживает ли Whereby GDPR и хранение данных в EU?

Да. Whereby - норвежская компания, серверы в EU. Enterprise-план гарантирует Data Processing Agreement (DPA) по GDPR. Записи хранятся в EU-регионе. Это одно из ключевых преимуществ перед Zoom для европейских B2B-компаний.

Работает ли Whereby в браузере без установки приложения?

Да - это ключевое отличие Whereby от Zoom. Встреча открывается в браузере, не нужны плагины или приложения. Клиент просто переходит по ссылке. Поддерживаются Chrome, Firefox, Edge, Safari.

Итог

Kommo + Whereby Embedded - видеозвонки из воронки:

  • Bearer token, POST /v1/meetings с endDate и roomNamePrefix
  • Ответ: roomUrl (клиент) + hostRoomUrl (менеджер-хост)
  • Сохранять meetingId в Kommo для удаления при закрытии сделки
  • Комнаты не удаляются сами - явно удалять через DELETE /v1/meetings/{id}
  • EU-серверы, GDPR, без приложения для клиента

Если ваша команда проводит демо-звонки через Kommo и нужно автоматизировать создание Whereby-комнат - опишите задачу команде Exceltic.dev.

Ещё статьи

Все →