Twilio/Telnyx/Exotel serializers crash the whole call on a malformed WebSocket message
pipecat version
1.9.1.dev318 (built from main)
Python version
3.12.14
Operating System
macOS 15 (not OS-specific — this is a pure message-parsing bug)
Issue description
TwilioFrameSerializer, TelnyxFrameSerializer, and ExotelFrameSerializer's deserialize()
parse incoming WebSocket data with a bare json.loads(data) and then index into the result with
message["event"] / message["media"]["payload"] — no guard against malformed JSON or an
unexpected message shape.
Both WebSocket transports (transports/websocket/fastapi.py and .../server.py) wrap their
entire receive loop in a single try/except Exception. Any exception raised inside
deserialize() breaks the loop, logs "exception receiving data", and falls through to
if not self._client.is_closing: trigger_client_disconnected() — i.e. a single malformed frame
is treated exactly like the caller hanging up.
Plivo, Vonage, and Genesys already guard against this (try/except json.JSONDecodeError plus
.get() chains instead of bracket indexing) — Twilio, Telnyx, and Exotel are the three siblings
that don't.
Reproduction steps
- Start a bot on any of the three affected telephony transports (Twilio, Telnyx, or Exotel).
- Have the carrier (or a proxy/test harness) send one malformed or unexpectedly-shaped WebSocket
text frame — e.g. non-JSON, or a well-formed
{"event": "media"}with nomediakey. - Observe the call end immediately, as if the caller had hung up.
Minimal reproducible example
# Isolates the exact code path in TwilioFrameSerializer.deserialize (identical shape in
# TelnyxFrameSerializer and ExotelFrameSerializer) — no live call or WebSocket needed.
import json
message = json.loads("not json") # raises json.decoder.JSONDecodeError
# A well-formed but unexpectedly-shaped "media" event has the same effect:
message = {"event": "media", "media": {}}
payload_base64 = message["media"]["payload"] # raises KeyError: 'payload'Eval scenario or recordings (optional)
No response
Expected behavior
A malformed or unexpectedly-shaped message should be logged and ignored, the same way Plivo, Vonage, and Genesys already handle it — not propagate an exception that tears down the call.
Actual behavior
deserialize() raises JSONDecodeError/KeyError, which the transport's receive loop treats as a client disconnect, silently ending the call.
Logs
Traceback (most recent call last):
File "<string>", line 2, in <module>
message = json.loads("not json")
^^^^^^^^^^^^^^^^^^^^^^^
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)Before you submit
- I have personally reproduced this issue on the latest Pipecat release (or
main). - I have searched existing issues for this bug.
- If AI tools helped write this report, I have verified the behavior myself and will personally answer follow-up questions.
Source: pipecat-ai/pipecat