Voice AI

Build a Non-Manipulative AI Subscription Cancel-Save Agent with Telnyx

Subscription cancel-save flows are easy to over-engineer into manipulative dark patterns, and easy to under-engineer into "your subscription has been cancelled, goodbye" experiences that lose revenue. A good cancel-save agent does three things: classifies the reason, offers one relevant save option, and respects a direct cancellation request.

The canonical code example is in the Telnyx code examples repo:

https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-subscription-cancel-save-retention-agent-python

What This Example Builds

This Python Flask example combines Telnyx Voice, AI Inference, and Messaging into a cancel-save workflow that:

  • Verifies the customer by inbound caller ID
  • Asks why they want to cancel
  • Classifies the reason with AI Inference into one of seven categories: too_expensive, not_using, missing_feature, support_issue, competitor_switch, temporary_pause, other
  • Detects angry customers (lawyer, sue, fraud, chargeback, BBB) and transfers immediately to a human
  • Offers one save option based on the reason (discount, onboarding call, roadmap note, support callback, comparison call, pause)
  • Records the outcome (saved, cancelled, paused, transferred, needs_followup) and updates the customer record
  • Tracks call hangups as needs_followup so reps can call back

Why This Is A Useful Voice AI Example

This example is small enough to read in one sitting, but it covers the moving parts a developer needs for a real cancel-save workflow:

  • Single-prompt AI Inference classification that returns structured JSON (reason, sentiment, wants_human, wants_pause, summary)
  • An offer policy that maps reason to a single offer and a default outcome
  • Hard-coded override for urgent phrases so angry or threatening customers transfer immediately
  • Direct-cancel short-circuit so a customer who says "cancel now" never sees an offer
  • One clarifying prompt on ambiguous yes/no so the conversation does not loop
  • Idempotent webhook handling so retries do not double-log cases

That makes it a good starting point for any inbound voice agent that needs to classify a request into a fixed set of buckets and apply a different policy per bucket.

Products Used

The example metadata lists:

telnyx_products: [Voice, AI Inference, Messaging]
language: python
framework: flask

Voice handles the call and the TTS. AI Inference classifies the reason. Messaging sends confirmation SMS.

Architecture

inbound phone call
  -> telnyx voice api webhook
  -> flask app
  -> look up customer by caller id
  -> if not found or already cancelled: end politely
  -> ask why cancel
  -> ai inference: classify reason + sentiment
  -> urgent phrases (lawyer, sue, fraud) -> transfer immediately
  -> direct cancel phrases (cancel now, etc) -> cancel immediately
  -> else: offer one save option from OFFER_POLICY
  -> yes: apply outcome (saved/paused/needs_followup) and update customer
  -> no: cancel gracefully
  -> ambiguous: ask one clarifying question, never loop
  -> hangup before resolution: outcome = needs_followup

State lives in memory (customers, retention_cases, calls) for the demo. For production, wire the customer and case stores into your billing system (Stripe, Recurly, Chargebee) so a saved outcome actually applies the discount and a paused outcome defers the next invoice.

Run The Example

git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/ai-subscription-cancel-save-retention-agent-python
cp .env.example .env
pip install -r requirements.txt
python app.py

Fill in the environment variables:

TELNYX_API_KEY=KEY...
TELNYX_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----..."
MAIN_NUMBER=+1...
CONNECTION_ID=...
HUMAN_ESCALATION_NUMBER=+1...
AI_MODEL=moonshotai/Kimi-K2.6
PORT=5000

Expose the local webhook:

ngrok http 5000

Configure the Voice API application webhook URL:

https://<ngrok-id>.ngrok-free.app/webhooks/voice

Demo Script

Seed a customer first:

curl -X POST http://localhost:5000/customers \
  -H "Content-Type: application/json" \
  -d '{"customer_id": "CUST-001", "name": "Jordan", "phone": "+15551112233", "plan": "pro"}'

Call the Telnyx number from +15551112233.

hi
i want to cancel my subscription
it's too expensive
yes please do that

Expected result:

  • Case is recorded with outcome: saved, reason: too_expensive
  • Customer status flips to active with the save applied

Try the no path next:

i want to cancel my subscription
it's too expensive
no thanks

Expected result:

  • Case is recorded with outcome: cancelled
  • Customer status flips to cancelled with cancelled_at timestamp

Try the transfer path:

i want to cancel my subscription
i want a lawyer this is fraud

Expected result:

  • Case is recorded with outcome: transferred
  • Call is transferred to HUMAN_ESCALATION_NUMBER

Inspect the cases:

curl http://localhost:5000/retention-cases | python3 -m json.tool

Production Considerations

Before using this pattern in production, add:

  • Persistent storage: replace the in-memory dicts with your billing-system integration
  • Real customer verification: replace the caller-ID match with a one-time code flow
  • Consent recording: add record_channels: "dual" on the answer action for compliance audits
  • Real offer application: wire offer acceptance into billing so a saved outcome actually applies the discount
  • Real pause: wire paused into billing to defer the next invoice
  • Multi-language system prompts and TTS voices per customer locale

Frequently Asked Questions

What does this tutorial cover?
Subscription cancel-save flows are easy to over-engineer into manipulative dark patterns, and easy to under-engineer into "your subscription has been cancelled, goodbye" experiences that lose revenue. A good cancel-save agent does three things: class
What is what this example builds?
This Python Flask example combines Telnyx Voice, AI Inference, and Messaging into a cancel-save workflow that:
What is why this is a useful voice ai example?
This example is small enough to read in one sitting, but it covers the moving parts a developer needs for a real cancel-save workflow:
What prerequisites do I need?
You need a Telnyx account with an API key. Check the Telnyx Portal (portal.telnyx.com) to create credentials, then set environment variables as shown in the setup instructions.

Resources

  • Code example: https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-subscription-cancel-save-retention-agent-python
  • Voice docs: https://developers.telnyx.com/docs/voice/call-control
  • AI Inference docs: https://developers.telnyx.com/docs/inference
  • Messaging docs: https://developers.telnyx.com/docs/messaging
  • Telnyx Portal: https://portal.telnyx.com

Ready to build with low-latency voice AI?

Join developers building the future of real-time conversations