Telnyx - enterprise-grade CPaaS (Communications Platform as a Service): программируемые SMS, звонки, номера, SIP trunking, AI-транскрипция. Позиционируется как более надёжная и дешёвая альтернатива Twilio с собственной глобальной сетью (не перепродаёт у операторов). Telnyx API использует Bearer token. SMS: POST /v2/messages. Голос: POST /v2/calls. Webhooks: доставляются на WebHook URL или через WebSocket (TeXML - XML-инструкции для управления звонком).
Для Kommo кастомная интеграция с Telnyx позволяет: отправлять SMS при смене этапа, логировать входящие SMS в карточку, инициировать звонок и получать запись разговора.
TeXML - XML-диалект Telnyx для управления звонками: аналог TwiML (Twilio) и PHML (Plivo). Совместим с TwiML на уровне большинства команд.
Сравнение с Twilio и Plivo
| Telnyx | Twilio | Plivo | |
|---|---|---|---|
| SMS US | $0.004 | $0.0079 | $0.0035 |
| Голос US | $0.007/мин | $0.0135/мин | $0.013/мин |
| Инфраструктура | Собственная сеть | Перепродажа | Перепродажа |
| Enterprise SLA | 99.999% | 99.95% | 99.9% |
| 10DLC | Да | Да | Да |
Telnyx дороже Plivo по SMS, но дешевле Twilio по голосу и SMS, и имеет собственную глобальную сеть.
Реализация: SMS при смене этапа
import requests, os, hmac, hashlib, base64
from flask import Flask, request, jsonify
app = Flask(__name__)
TELNYX_API_KEY = os.environ["TELNYX_API_KEY"] # KEY01234... Bearer token
TELNYX_FROM = os.environ["TELNYX_PHONE_NUMBER"] # +1xxxxxxxxxx
TELNYX_PROFILE_ID = os.environ.get("TELNYX_MESSAGING_PROFILE_ID", "")
KOMMO_SUBDOMAIN = os.environ["KOMMO_SUBDOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
SMS_STAGE_ID = int(os.environ["KOMMO_SMS_STAGE_ID"])
TELNYX_BASE = "https://api.telnyx.com/v2"
TELNYX_HDR = {"Authorization": f"Bearer {TELNYX_API_KEY}", "Content-Type": "application/json"}
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_name(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_telnyx_sms(to: str, text: str) -> str:
payload = {
"from": TELNYX_FROM,
"to": to,
"text": text,
"type": "SMS",
}
if TELNYX_PROFILE_ID:
payload["messaging_profile_id"] = TELNYX_PROFILE_ID
r = requests.post(f"{TELNYX_BASE}/messages", headers=TELNYX_HDR, json=payload)
r.raise_for_status()
return r.json().get("data", {}).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_name(lead_id)
if not phone:
continue
if not phone.startswith("+"):
phone = "+" + "".join(c for c in phone if c.isdigit())
first = name.split()[0] if name else ""
text = f"Добрый день{', ' + first if first else ''}! Ваша заявка рассмотрена. Ожидайте звонка менеджера."
msg_id = send_telnyx_sms(phone, text)
add_note(lead_id, f"Telnyx SMS отправлен. Message ID: {msg_id}")
return jsonify({"status": "ok"}), 200
Реализация: входящие SMS и AI-транскрипция звонков
def verify_telnyx_signature(body: bytes, sig_b64: str, ts: str) -> bool:
# Telnyx: HMAC-SHA256(timestamp + body) с public key (Ed25519)
# Упрощённая версия: проверяем через webhook signing secret
secret = os.environ.get("TELNYX_WEBHOOK_SECRET", "")
if not secret:
return True # если secret не настроен - пропускаем верификацию
msg = (ts + body.decode("utf-8")).encode()
digest = base64.b64encode(
hmac.new(secret.encode(), msg, hashlib.sha256).digest()
).decode()
return hmac.compare_digest(digest, sig_b64)
@app.route("/webhooks/telnyx", methods=["POST"])
def telnyx_webhook():
event = request.json or {}
ev_type = event.get("data", {}).get("event_type", "")
payload = event.get("data", {}).get("payload", {})
if ev_type == "message.received":
# Входящий SMS
from_num = payload.get("from", {}).get("phone_number", "")
text = payload.get("text", "")
if from_num and text:
lead_id = find_lead_by_phone(from_num)
if lead_id:
add_note(lead_id, f"Входящий SMS от {from_num}: {text}")
elif ev_type == "call.transcription":
# AI-транскрипция завершённого звонка
transcript = payload.get("transcription_data", {}).get("transcription", "")
call_leg_id = payload.get("call_leg_id", "")
lead_id = phone_to_lead.get(call_leg_id)
if lead_id and transcript:
add_note(int(lead_id), f"Telnyx AI транскрипт:
{transcript[:2000]}")
elif ev_type == "call.recording.saved":
# Запись звонка готова
recording_url = payload.get("recording_urls", {}).get("mp3", "")
call_leg_id = payload.get("call_leg_id", "")
lead_id = phone_to_lead.get(call_leg_id)
if lead_id and recording_url:
add_note(int(lead_id), f"Telnyx запись звонка: {recording_url}")
return jsonify({"status": "ok"}), 200
phone_to_lead = {} # {call_leg_id -> lead_id} в production: Redis
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
Telnyx AI Transcription
Telnyx предлагает встроенную AI-транскрипцию через TeXML команду <Record transcribe="true">. После завершения записи Telnyx отправляет webhook call.transcription с полным текстом разговора. Стоимость транскрипции: $0.005/мин (в дополнение к стоимости записи).
<Response>
<Record transcribe="true"
transcriptionCallback="https://your-server.com/webhooks/telnyx"
maxLength="3600"
playBeep="true" />
</Response>
Для кого актуально
Enterprise B2B компании с высоким объёмом звонков (100+/мес) и требованиями к SLA (Telnyx предоставляет SLA 99.999%). Особенно подходит для компаний, чьи текущие Twilio-расходы превышают $1 000/мес - Telnyx может снизить на 30-50%.
Аналогичные интеграции описаны для Kommo + Twilio и Kommo + Plivo.
Часто задаваемые вопросы
Как Telnyx отличается от Twilio технически?
Telnyx строит на собственной Tier-1 сети - не перепродаёт трафик через AT&T или T-Mobile напрямую, а работает с ними как равный carrier. Это даёт меньшую задержку и меньше промежуточных хопов. Для программируемого API отличий минимум - Telnyx намеренно сделал TeXML совместимым с TwiML для упрощения миграции.
Поддерживает ли Telnyx WhatsApp Business API?
Да, Telnyx предоставляет WhatsApp API через официальное партнёрство с Meta. Настройка аналогична - через Telnyx Portal. Для Kommo интеграция с WhatsApp через Telnyx - альтернатива прямому подключению через Meta.
Как мигрировать с Twilio на Telnyx?
Telnyx поддерживает перенос номеров (number porting) из Twilio. TeXML совместим с TwiML: большинство webhook-handlers работают без изменений. Измените базовый URL (api.telnyx.com вместо api.twilio.com) и переменные Auth. Среднее время миграции для API-интеграций: 1-2 дня.
Итог
Kommo + Telnyx - enterprise CPaaS из воронки:
- Bearer token
TELNYX_API_KEY, SMS черезPOST /v2/messages - Входящие SMS webhook
message.received-> find lead by phone -> note - AI транскрипция: TeXML
<Record transcribe="true">->call.transcriptionwebhook - Запись звонка:
call.recording.saved-> recording_url -> note - Дешевле Twilio на 30-50%, SLA 99.999%, собственная глобальная сеть
Если нужна миграция с Twilio на Telnyx или кастомная интеграция с Kommo - опишите задачу команде Exceltic.dev.