added exotel inbound websocket endpoint#219
Conversation
- also removed exotel inbound webhook
📝 WalkthroughWalkthroughThis pull request replaces Exotel's HTTP webhook handler with a WebSocket endpoint that handles inbound connections, validates call data, normalizes phone numbers, and integrates with the Pipecat conversation pipeline. Additionally, a new utility function normalizes Indian phone numbers to E.164 format. Changes
Sequence DiagramsequenceDiagram
participant Client as Exotel Client
participant WS as WebSocket Handler
participant Validate as Validation
participant Agent as Agent Lookup
participant Config as Config Retrieval
participant Transport as FastAPI Transport
participant Pipeline as Pipecat Pipeline
Client->>WS: WebSocket connection
WS->>Validate: Validate transport/type
Validate->>Validate: Extract call data
Validate->>Validate: Normalize phone numbers
Validate->>Agent: Lookup agent by number
Agent-->>WS: Agent info
WS->>Config: Retrieve config
Config-->>WS: Configuration
WS->>Transport: Setup FastAPIWebsocketTransport
Transport->>Pipeline: Initiate conversation
Pipeline-->>Client: Start conversation flow
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@wavefront/server/apps/call_processing/call_processing/controllers/webhook_controller.py`:
- Around line 413-416: The code directly indexes call_data['stream_id'] when
constructing ExotelFrameSerializer which will raise KeyError if stream_id is
missing; change to use call_data.get('stream_id') and add an explicit presence
check before creating ExotelFrameSerializer (e.g., validate stream_id is not
None/empty) and return or raise a controlled error/response if absent so
behavior matches how call_id is handled and avoids an unhandled KeyError in the
serializer construction.
🧹 Nitpick comments (4)
wavefront/server/apps/call_processing/call_processing/utils.py (1)
36-56: Consider stripping non-digit characters and validating digit-only input.The function only strips leading/trailing whitespace but doesn't handle numbers containing dashes, spaces, or parentheses (e.g.,
"091-1234567890"). If Exotel ever sends formatted numbers, they'll fall through unrecognized. Also, a non-numeric input like"abcdefghij"(10 chars) would get+91prepended.💡 Optional hardening
# Remove any whitespace phone_number = phone_number.strip() + # Remove common formatting characters + phone_number = phone_number.replace('-', '').replace(' ', '').replace('(', '').replace(')', '') + + # Validate that remaining string contains only digits (with optional leading +) + clean = phone_number.lstrip('+') + if not clean.isdigit(): + return phone_number + # If already in E.164 format with +91, return as iswavefront/server/apps/call_processing/call_processing/controllers/webhook_controller.py (3)
1-5: Module docstring is outdated — no longer Twilio-only.The module now also handles Exotel WebSocket endpoints. Update the docstring to reflect the broader scope.
312-455: Consider extracting shared transport/pipeline setup to reduce duplication.The transport creation (VAD params,
FastAPIWebsocketTransport) and pipeline execution (PipecatService().run_conversation(...)) blocks inexotel_inbound_websocketare nearly identical to those inwebsocket_endpoint(lines 275–304). As more providers are added, this duplication will grow. A shared helper (e.g.,_create_transport_and_run_pipeline(websocket, serializer, configs, customer_number)) would consolidate this.
363-366: Missing validation forfrom_number(caller number).
to_numberis validated (line 363), butfrom_numbercan be empty and still gets normalized and passed ascustomer_number. While the existingwebsocket_endpointhas a similar pattern (lines 232–236 log a warning for emptycustomer_number), consider adding at least a warning log here for traceability when the caller number is missing.
| serializer = ExotelFrameSerializer( | ||
| stream_sid=call_data['stream_id'], | ||
| call_sid=call_data.get('call_id'), | ||
| ) |
There was a problem hiding this comment.
call_data['stream_id'] will raise KeyError if missing.
Line 414 uses direct dict access for stream_id, while line 415 uses .get('call_id'). If the Exotel stream message is malformed and stream_id is absent, this will raise an unhandled KeyError (caught only by the broad except Exception). Consider using .get() with validation or adding an explicit check, consistent with how other fields are handled.
Suggested improvement
+ stream_id = call_data.get('stream_id')
+ if not stream_id:
+ logger.error('No stream_id found in Exotel call data')
+ await websocket.close(code=1008, reason='Missing stream_id in call data')
+ return
+
# Create Exotel frame serializer
serializer = ExotelFrameSerializer(
- stream_sid=call_data['stream_id'],
+ stream_sid=stream_id,
call_sid=call_data.get('call_id'),
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| serializer = ExotelFrameSerializer( | |
| stream_sid=call_data['stream_id'], | |
| call_sid=call_data.get('call_id'), | |
| ) | |
| stream_id = call_data.get('stream_id') | |
| if not stream_id: | |
| logger.error('No stream_id found in Exotel call data') | |
| await websocket.close(code=1008, reason='Missing stream_id in call data') | |
| return | |
| serializer = ExotelFrameSerializer( | |
| stream_sid=stream_id, | |
| call_sid=call_data.get('call_id'), | |
| ) |
🤖 Prompt for AI Agents
In
`@wavefront/server/apps/call_processing/call_processing/controllers/webhook_controller.py`
around lines 413 - 416, The code directly indexes call_data['stream_id'] when
constructing ExotelFrameSerializer which will raise KeyError if stream_id is
missing; change to use call_data.get('stream_id') and add an explicit presence
check before creating ExotelFrameSerializer (e.g., validate stream_id is not
None/empty) and return or raise a controlled error/response if absent so
behavior matches how call_id is handled and avoids an unhandled KeyError in the
serializer construction.
Summary by CodeRabbit
New Features
Refactor