diff --git a/api/services/telephony/providers/telnyx/provider.py b/api/services/telephony/providers/telnyx/provider.py index dcbc515..1c9c41f 100644 --- a/api/services/telephony/providers/telnyx/provider.py +++ b/api/services/telephony/providers/telnyx/provider.py @@ -25,6 +25,14 @@ if TYPE_CHECKING: from fastapi import WebSocket +def normalize_event_type(event_type: str) -> str: + """Telnyx delivers event types with either dots or underscores + (e.g. ``streaming.started`` vs ``streaming_started``). Normalize to the + dotted form so all downstream matching can use a single canonical shape. + """ + return (event_type or "").replace("_", ".") + + class TelnyxProvider(TelephonyProvider): """ Telnyx implementation of TelephonyProvider. @@ -187,7 +195,7 @@ class TelnyxProvider(TelephonyProvider): def parse_status_callback(self, data: Dict[str, Any]) -> Dict[str, Any]: """Parse Telnyx webhook event data into generic format.""" event_data = data.get("data", data) - event_type = event_data.get("event_type", "") + event_type = normalize_event_type(event_data.get("event_type", "")) payload = event_data.get("payload", {}) status = self._resolve_status(event_type, payload) @@ -378,7 +386,7 @@ class TelnyxProvider(TelephonyProvider): # Check for Telnyx event structure data = webhook_data.get("data", {}) if data.get("record_type") == "event" and "event_type" in data: - event_type = data.get("event_type", "") + event_type = normalize_event_type(data.get("event_type", "")) if event_type.startswith("call."): return True @@ -401,7 +409,7 @@ class TelnyxProvider(TelephonyProvider): from_number=TelnyxProvider.normalize_phone_number(payload.get("from", "")), to_number=TelnyxProvider.normalize_phone_number(payload.get("to", "")), direction=direction, - call_status=data.get("event_type", ""), + call_status=normalize_event_type(data.get("event_type", "")), account_id=payload.get("connection_id"), raw_data=webhook_data, ) diff --git a/api/services/telephony/providers/telnyx/routes.py b/api/services/telephony/providers/telnyx/routes.py index 5e02030..1e878df 100644 --- a/api/services/telephony/providers/telnyx/routes.py +++ b/api/services/telephony/providers/telnyx/routes.py @@ -11,6 +11,7 @@ from loguru import logger from api.db import db_client from api.services.telephony.factory import get_telephony_provider +from api.services.telephony.providers.telnyx.provider import normalize_event_type from api.services.telephony.status_processor import ( StatusCallbackRequest, _process_status_update, @@ -37,9 +38,11 @@ async def handle_telnyx_events( f"[run {workflow_run_id}] Received Telnyx event: {json.dumps(event_data)}" ) - # Extract event type from Telnyx envelope + # Extract event type from Telnyx envelope. Telnyx sometimes delivers the + # type with underscores (``streaming_started``) instead of dots + # (``streaming.started``); normalize so downstream comparisons match either. data = event_data.get("data", {}) - event_type = data.get("event_type", "") + event_type = normalize_event_type(data.get("event_type", "")) # Skip streaming events — they're informational only if event_type in ("streaming.started", "streaming.stopped"): diff --git a/api/services/telephony/providers/telnyx/transport.py b/api/services/telephony/providers/telnyx/transport.py index fb603f5..f47d0c3 100644 --- a/api/services/telephony/providers/telnyx/transport.py +++ b/api/services/telephony/providers/telnyx/transport.py @@ -45,8 +45,8 @@ async def create_transport( stream_id=stream_id, call_control_id=call_control_id, api_key=api_key, - inbound_encoding="PCMU", # Dograh → Telnyx; matches stream_bidirectional_codec - outbound_encoding=encoding, # Telnyx → Dograh; from media_format.encoding + inbound_encoding="PCMU", # Dograh → Telnyx; matches stream_bidirectional_codec + outbound_encoding=encoding, # Telnyx → Dograh; from media_format.encoding ) mixer = await build_audio_out_mixer(