Kommo + Sinch: SMS и звонки из воронки через облачную платформу коммуникаций

Sinch - cloud communications platform: SMS, голосовые звонки, WhatsApp Business, email. Конкурент Twilio и Telnyx, ориентированный на EU и скандинавские рынки (штаб-квартира в Стокгольме). Ключевые отличия: собственная SS7-сеть в Европе, SLA 99.99%, прямые роуминговые соглашения с EU-операторами.

Sinch SMS API использует Basic Auth (Service Plan ID + API Token) или Bearer. Голосовые звонки - Sinch Voice REST API. Для Kommo интеграция позволяет: отправлять SMS при смене этапа, получать входящие SMS как note в карточке, инициировать автоматический TTS-звонок (text-to-speech).

Sinch SMS работает через Messaging API: POST /xms/v1/{service_plan_id}/batches - отправить SMS одному или группе. Входящие сообщения - POST /v1/sms/inbounds или через webhook. Delivery reports - отдельный webhook endpoint.

Sinch MO (Mobile Originated) - входящее SMS (от клиента к вашему номеру). MT (Mobile Terminated) - исходящее SMS (от вас к клиенту). Обе направления обрабатываются через один Service Plan.

Реализация: исходящий SMS при смене этапа

import requests, os, hmac, hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)

SINCH_SERVICE_PLAN_ID = os.environ["SINCH_SERVICE_PLAN_ID"]
SINCH_API_TOKEN       = os.environ["SINCH_API_TOKEN"]
SINCH_FROM_NUMBER     = os.environ["SINCH_FROM_NUMBER"]  # +46...
SINCH_BASE            = f"https://us.sms.api.sinch.com/xms/v1/{SINCH_SERVICE_PLAN_ID}"
SINCH_AUTH            = (SINCH_SERVICE_PLAN_ID, SINCH_API_TOKEN)

KOMMO_SUBDOMAIN  = os.environ["KOMMO_SUBDOMAIN"]
KOMMO_TOKEN      = os.environ["KOMMO_ACCESS_TOKEN"]
SMS_STAGE_ID     = int(os.environ["KOMMO_SMS_STAGE_ID"])

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

def get_contact_phone(lead_id: int) -> tuple[str, str]:
    r = requests.get(
        f"{KOMMO_BASE}/leads/{lead_id}",
        headers=KOMMO_HDR,
        params={"with": "contacts"},
    )
    contacts = r.json().get("_embedded", {}).get("contacts", [])
    if not contacts:
        return "", ""
    rc = requests.get(
        f"{KOMMO_BASE}/contacts/{contacts[0]['id']}",
        headers=KOMMO_HDR,
        params={"with": "custom_fields_values"},
    )
    c     = rc.json()
    phone = ""
    for cf in c.get("custom_fields_values", []) or []:
        if cf.get("field_code") == "PHONE":
            vals = cf.get("values", [])
            if vals:
                phone = vals[0].get("value", "")
                break
    return c.get("name", ""), phone

def send_sms(to: str, body: str) -> str:
    r = requests.post(
        f"{SINCH_BASE}/batches",
        auth=SINCH_AUTH,
        json={
            "from": SINCH_FROM_NUMBER,
            "to":   [to],
            "body": body,
        },
    )
    r.raise_for_status()
    return r.json().get("id", "")

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},
        }],
    )

@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 != SMS_STAGE_ID:
            continue

        name, phone = get_contact_phone(lead_id)
        if not phone:
            continue

        if not phone.startswith("+"):
            digits = "".join(c for c in phone if c.isdigit())
            phone  = "+" + digits

        first = name.split()[0] if name else ""
        text  = (
            f"Hello{', ' + first if first else ''}! "
            f"Your inquiry has been reviewed. Our manager will contact you shortly."
        )
        batch_id = send_sms(phone, text)
        add_note(lead_id, f"Sinch SMS отправлен ({batch_id}) на {phone}.")

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

Реализация: входящие SMS -> note в Kommo

@app.route("/webhooks/sinch/inbound", methods=["POST"])
def sinch_inbound():
    event    = request.json or {}
    ev_type  = event.get("type", "")

    if ev_type != "mo_text":  # mo_text = Mobile Originated text SMS
        return jsonify({"status": "ignored"}), 200

    from_num = event.get("from", "")
    body     = event.get("body", "")
    received = event.get("received_at", "")

    lead_id = find_lead_by_phone(from_num)
    if not lead_id:
        return jsonify({"status": "no_lead"}), 200

    add_note(lead_id, f"Sinch входящий SMS от {from_num}: {body}")
    return jsonify({"status": "ok"}), 200

def find_lead_by_phone(phone: str) -> int | None:
    r = requests.get(
        f"{KOMMO_BASE}/contacts",
        headers=KOMMO_HDR,
        params={"query": phone, "limit": 5},
    )
    contacts = r.json().get("_embedded", {}).get("contacts", []) or []
    if not contacts:
        return None
    r2 = requests.get(
        f"{KOMMO_BASE}/leads",
        headers=KOMMO_HDR,
        params={"filter[contact_id]": contacts[0]["id"], "limit": 1},
    )
    leads = r2.json().get("_embedded", {}).get("leads", []) or []
    return leads[0]["id"] if leads else None

@app.route("/webhooks/sinch/delivery", methods=["POST"])
def sinch_delivery():
    # Delivery report для исходящих SMS
    event  = request.json or {}
    status = event.get("status", "")  # Delivered, Failed, etc.
    batch_id = event.get("batch_id", "")
    # Логировать для мониторинга доставки
    return jsonify({"status": "ok"}), 200

Голосовые звонки: TTS (Text-to-Speech)

SINCH_APP_KEY    = os.environ["SINCH_APP_KEY"]
SINCH_APP_SECRET = os.environ["SINCH_APP_SECRET"]
SINCH_VOICE_BASE = "https://calling.api.sinch.com/calling/v1"

def make_tts_call(to: str, message: str) -> str:
    import base64
    auth_str = base64.b64encode(f"{SINCH_APP_KEY}:{SINCH_APP_SECRET}".encode()).decode()
    r = requests.post(
        f"{SINCH_VOICE_BASE}/callouts/tts",
        headers={
            "Authorization": f"Basic {auth_str}",
            "Content-Type":  "application/json",
        },
        json={
            "method":  "ttsCallout",
            "ttsCallout": {
                "cli":      SINCH_FROM_NUMBER,
                "destination": {"type": "number", "endpoint": to},
                "locale":   "en-US",
                "text":     message,
            },
        },
    )
    r.raise_for_status()
    return r.json().get("callId", "")

Регионы и номера

Sinch предоставляет номера в 60+ странах. Для EU: отдельный regional endpoint eu.sms.api.sinch.com (против us.sms.api.sinch.com). Если клиенты преимущественно в Европе - используйте EU endpoint для меньшей задержки и соответствия GDPR.

# EU endpoint
SINCH_BASE = f"https://eu.sms.api.sinch.com/xms/v1/{SINCH_SERVICE_PLAN_ID}"

10DLC регистрация (для США) и TF (Toll-Free) - через Sinch Dashboard. Для EU-номеров отдельная процедура верификации бизнеса.

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

B2B компании с EU-клиентами, которым нужна альтернатива Twilio с европейской инфраструктурой. Sinch особенно хорошо работает для SMS в Скандинавии, Германии, Нидерландах и Великобритании. Собственная SS7-сеть означает меньше промежуточных операторов и более высокую доставляемость.

Сравнение альтернатив: Kommo + Telnyx (низкие цены, Tier-1 сеть), Kommo + Plivo (простой API).

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

Как получить Sinch SMS Number для отправки в Европу?

Sinch Dashboard -> Numbers -> Browse -> выбрать страну -> купить. Для SMS в Германии нужен верифицированный sender ID (буквенный, например “MyCompany”). Для Великобритании - числовой shared short code или dedicated long code. Процесс верификации: 1-3 рабочих дня.

Поддерживает ли Sinch WhatsApp Business API?

Да, через Sinch Conversation API - единый API для SMS, WhatsApp, RCS, Viber. Настройка WhatsApp через Sinch занимает 1-2 недели (верификация мета). Для Kommo интеграция с WhatsApp через Sinch - альтернатива прямому подключению через Meta или готовым Kommo-коннекторам.

Как Sinch обеспечивает GDPR-совместимость для EU-SMS?

Sinch GDPR: данные обрабатываются в EU датацентрах при использовании EU endpoint. DPA (Data Processing Agreement) доступно по запросу. Логи сообщений хранятся 7 дней по умолчанию (настраивается). Sinch имеет ISO 27001 сертификацию и регулярные аудиты безопасности.

Итог

Kommo + Sinch - cloud communications с EU-инфраструктурой:

  • Basic Auth (Service Plan ID + API Token), POST /xms/v1/{service_plan_id}/batches
  • EU endpoint: eu.sms.api.sinch.com для GDPR и меньшей задержки
  • Входящие SMS: webhook mo_text -> find lead by phone -> note
  • TTS звонок: Sinch Voice API, Basic Auth с App Key + App Secret
  • Delivery reports через отдельный webhook endpoint для мониторинга доставки

Если нужна интеграция Kommo с Sinch или другой облачной платформой коммуникаций - опишите задачу команде Exceltic.dev.

Ещё статьи

Все →