Skip to content

added exotel inbound websocket endpoint#219

Open
rootflo-hardik wants to merge 1 commit intodevelopfrom
CU-86d1ztnzg-exotel-inbound-websocket
Open

added exotel inbound websocket endpoint#219
rootflo-hardik wants to merge 1 commit intodevelopfrom
CU-86d1ztnzg-exotel-inbound-websocket

Conversation

@rootflo-hardik
Copy link
Contributor

@rootflo-hardik rootflo-hardik commented Feb 16, 2026

  • also removed exotel inbound webhook

Summary by CodeRabbit

  • New Features

    • WebSocket support for Exotel inbound call connections.
    • Added Indian phone number normalization to E.164 standard format.
  • Refactor

    • Migrated Exotel inbound call processing from HTTP to WebSocket protocol.

- also removed exotel inbound webhook
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Exotel WebSocket Migration
wavefront/server/apps/call_processing/call_processing/controllers/webhook_controller.py
Removed HTTP webhook handler (exotel_inbound_webhook) and replaced with WebSocket endpoint (exotel_inbound_websocket). New implementation validates WebSocket transport, extracts call data, normalizes phone numbers, performs agent lookup, configures Pipecat transport, and initiates conversation pipeline with error handling.
Phone Number Utility
wavefront/server/apps/call_processing/call_processing/utils.py
Added normalize_indian_phone_number() function that converts various Indian phone number formats (+91, 91, 0-prefixed, and 10-digit local) to E.164 format.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 From REST to WebSocket flows,
Our Exotel connection grows,
Phone numbers normalized with care,
Indian digits dance through the air,
A conversation pipeline fair! 🎯

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: adding an Exotel inbound WebSocket endpoint, which matches the primary modifications in webhook_controller.py.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch CU-86d1ztnzg-exotel-inbound-websocket

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 +91 prepended.

💡 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 is
wavefront/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 in exotel_inbound_websocket are nearly identical to those in websocket_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 for from_number (caller number).

to_number is validated (line 363), but from_number can be empty and still gets normalized and passed as customer_number. While the existing websocket_endpoint has a similar pattern (lines 232–236 log a warning for empty customer_number), consider adding at least a warning log here for traceability when the caller number is missing.

Comment on lines +413 to +416
serializer = ExotelFrameSerializer(
stream_sid=call_data['stream_id'],
call_sid=call_data.get('call_id'),
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant