Kommo + Telnyx: Enterprise SMS and Calls from the Pipeline via Programmable Telephony
Telnyx is an enterprise-grade CPaaS (Communications Platform as a Service): programmable SMS, voice calls, phone numbers, SIP trunking, and AI transcription. It is positioned as a more reliable and cost-effective alternative to Twilio, built on its own global network (rather than reselling from carriers). The Telnyx API uses Bearer token authentication. SMS: POST /v2/messages. Voice: POST /v2/calls. Webhooks: delivered to a WebHook URL or via WebSocket (TeXML - XML instructions for call control).
With a custom Kommo integration, Telnyx enables: sending SMS on stage change, logging inbound SMS to the deal card, initiating calls, and receiving call recordings.
TeXML is Telnyx’s XML dialect for call control - analogous to TwiML (Twilio) and PHML (Plivo). It is compatible with TwiML at the level of most commands.
Comparison with Twilio and Plivo
| Telnyx | Twilio | Plivo | |
|---|---|---|---|
| SMS US | $0.004 | $0.0079 | $0.0035 |
| Voice US | $0.007/min | $0.0135/min | $0.013/min |
| Infrastructure | Own network | Reseller | Reseller |
| Enterprise SLA | 99.999% | 99.95% | 99.9% |
| 10DLC | Yes | Yes | Yes |
Telnyx is more expensive than Plivo for SMS, but cheaper than Twilio for both voice and SMS, and operates its own global network.
Implementation: SMS on Stage Change
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"Hello{', ' + first if first else ''}! Your request has been reviewed. Our manager will call you shortly."
msg_id = send_telnyx_sms(phone, text)
add_note(lead_id, f"Telnyx SMS sent. Message ID: {msg_id}")
return jsonify({"status": "ok"}), 200
Implementation: Inbound SMS and AI Call Transcription
def verify_telnyx_signature(body: bytes, sig_b64: str, ts: str) -> bool:
# Telnyx: HMAC-SHA256(timestamp + body) with public key (Ed25519)
# Simplified version: verify via webhook signing secret
secret = os.environ.get("TELNYX_WEBHOOK_SECRET", "")
if not secret:
return True # skip verification if secret is not configured
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":
# Inbound 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"Inbound SMS from {from_num}: {text}")
elif ev_type == "call.transcription":
# AI transcription of completed call
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:\n{transcript[:2000]}")
elif ev_type == "call.recording.saved":
# Call recording is ready
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 call recording: {recording_url}")
return jsonify({"status": "ok"}), 200
phone_to_lead = {} # {call_leg_id -> lead_id} in 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 offers built-in AI transcription via the TeXML command <Record transcribe="true">. After the recording ends, Telnyx sends a call.transcription webhook with the full call transcript. Transcription cost: $0.005/min (in addition to the recording cost).
<Response>
<Record transcribe="true"
transcriptionCallback="https://your-server.com/webhooks/telnyx"
maxLength="3600"
playBeep="true" />
</Response>
Who This Is For
Enterprise B2B companies with high call volumes (100+/month) and SLA requirements (Telnyx offers a 99.999% SLA). Particularly well-suited for companies whose current Twilio spend exceeds $1,000/month - Telnyx can reduce costs by 30-50%.
Similar integrations are described for Kommo + Twilio and Kommo + Plivo.
Frequently Asked Questions
How does Telnyx differ from Twilio technically?
Telnyx builds on its own Tier-1 network - rather than reselling traffic through AT&T or T-Mobile, it peers with them as an equal carrier. This results in lower latency and fewer intermediate hops. From a programmable API perspective the differences are minimal - Telnyx intentionally made TeXML compatible with TwiML to simplify migration.
Does Telnyx support WhatsApp Business API?
Yes, Telnyx provides WhatsApp API access through an official Meta partnership. Setup is similar - via the Telnyx Portal. For Kommo, integrating WhatsApp through Telnyx is an alternative to a direct connection through Meta.
How do I migrate from Twilio to Telnyx?
Telnyx supports number porting from Twilio. TeXML is compatible with TwiML: most webhook handlers work without changes. Update the base URL (api.telnyx.com instead of api.twilio.com) and your auth variables. Average migration time for API integrations: 1-2 days.
Summary
Kommo + Telnyx - enterprise CPaaS from within the pipeline:
- Bearer token
TELNYX_API_KEY, SMS viaPOST /v2/messages - Inbound SMS webhook
message.received-> find lead by phone -> note - AI transcription: TeXML
<Record transcribe="true">->call.transcriptionwebhook - Call recording:
call.recording.saved-> recording_url -> note - 30-50% cheaper than Twilio, 99.999% SLA, own global network
If you need to migrate from Twilio to Telnyx or build a custom Kommo integration - describe your requirements to the Exceltic.dev team.