Traditional IVRs make callers do the routing work.
You call a business, listen to a menu, remember the options, press a number, and hope you picked the right path. That works, but it is not how people naturally ask for help.
Most callers already know what they want:
- "I need to pay my bill."
- "I want to upgrade my plan."
- "I need help with support."
The ai-powered-call-router example turns that sentence into the routing decision.
Code: https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-powered-call-router
What the app builds
This is a Python Flask app that handles inbound calls with Telnyx Call Control and classifies caller intent with Telnyx AI Inference.
The flow looks like this:
Inbound call
-> Telnyx sends a Call Control webhook
-> Flask verifies the webhook
-> app answers the call
-> app speaks a greeting
-> app gathers caller speech
-> AI classifies intent
-> app announces the transfer
-> Call Control transfers the caller
Instead of asking the caller to press buttons, the app asks what they need and routes based on their answer.
The core pieces
The example uses:
- Telnyx Call Control for answering, speaking, gathering, and transferring calls
- Telnyx AI Inference for intent classification
- Telnyx webhook verification for signed inbound events
- a simple route table for mapping intents to destinations
The source keeps the route table intentionally small:
ROUTE_TABLE = {
"billing": "+1XXXXXXXXXX",
"sales": "+1XXXXXXXXXX",
"support": "+1XXXXXXXXXX",
}
In a real app, those destinations might be queues, agents, contact center flows, or PBX extensions.
Gathering caller speech
Once the inbound call is answered, the app plays a greeting. After the greeting finishes, it starts speech capture using Call Control's AI gather action.
The important detail is that the app waits for the call.speak.ended event before starting the gather. That avoids the greeting and the caller's first response overlapping.
The gather step produces a call.ai_gather.ended event with the caller's utterance.
That text becomes the input to the intent classifier.
Classifying intent with AI
The app sends the caller's transcribed request to Telnyx AI Inference and asks the model to classify it into one of the supported routes.
The default model in the sample is:
meta-llama/Llama-3.3-70B-Instruct
The model does not need to write a long answer. It only needs to return a routing label such as:
billing
sales
support
That keeps the AI part narrow and practical. The LLM is not running the whole call center. It is doing one job: mapping natural language to a route.
Transferring the call
After intent classification, the app speaks a short announcement:
Transferring you to billing. Please hold.
Then it waits for that announcement to finish before calling the transfer action.
That makes the caller experience cleaner. They hear what is happening before the bridge is created.
The example uses a blind transfer: Telnyx dials the destination and connects the original caller once the destination answers.
Webhook verification
Voice automation should not trust random HTTP requests.
The app verifies inbound Telnyx webhooks with Ed25519 signature verification before processing the event:
event = unwrap_with_ed25519(
request.get_data(),
request.headers,
key=TELNYX_PUBLIC_KEY,
)
That means call actions like answer, gather, and transfer only run after the app verifies the webhook came from Telnyx.
Running it locally
Clone the examples repo:
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/ai-powered-call-router
Create a virtual environment and install dependencies:
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Create your .env file:
cp .env.example .env
Set the required values:
TELNYX_API_KEY=<your_telnyx_api_key>
TELNYX_PUBLIC_KEY=<your_telnyx_public_key>
TELNYX_CONNECTION_ID=<your_call_control_application_id>
AI_MODEL=meta-llama/Llama-3.3-70B-Instruct
PORT=5000
Then run:
python app.py
Expose your local server with a tunnel and point your Telnyx Call Control Application webhook to:
https://<your-tunnel-domain>/webhook
Why I like this pattern
This is a good example of where AI is useful without making the system vague.
The caller still moves through a deterministic telephony flow:
- answer the call
- gather speech
- classify intent
- look up a destination
- transfer the call
The model only handles the messy human-language part.
That is the sweet spot for a lot of production AI apps: use the LLM where language is ambiguous, but keep the workflow itself explicit and observable.
Production notes
Before using this with real callers, I would add:
- retries and fallback routing
- latency tracking for gather and classification
- route-level analytics
- auth around any admin/config endpoints
- call recording/transcript retention policy
- escalation rules for uncertain classifications
- monitoring for failed transfers
But the core idea is simple: callers should be able to say what they need, and your app should route them without forcing them through a rigid menu.
Resources
- Code example: https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-powered-call-router
- Telnyx AI toolkit: https://github.com/team-telnyx/ai
- Telnyx Call Control docs: https://developers.telnyx.com/docs/voice/programmable-voice/call-control-overview
- Telnyx AI Inference docs: https://developers.telnyx.com/docs/inference
- Webhook signing: https://developers.telnyx.com/development/api-fundamentals/webhooks/receiving-webhooks#webhook-signing
- Telnyx Portal: https://portal.telnyx.com
Top comments (0)