Complete Build Tutorial · Incoming Call to Menu Response
Deskline Programmable Voice IVR
An inbound phone menu system handling real Telnyx voice calls. Route customers through DTMF keypad prompts, speak business facts, and collect menu decisions. Built end-to-end from setup to live call with webhook verification, SQLite state management, deterministic testing, and eight quality gates.
A 1-week step-by-step tutorial covering setup, webhook verification, state management, voice commands, and call testing. Uses a fictional Brightside Device Repair shop; all data is synthetic and testing happens in rehearsal mode before first live call.
The problem
Inbound phone queues and menu systems are painful to build:
•Phone state spans multiple webhook events; no single request holds context
•Webhook order is not guaranteed; events may arrive out of sequence
•Testing requires real phone calls; unit tests are not enough
•Billing per call; mistakes are expensive to debug live
How it works
Deskline runs a FastAPI server that listens for Telnyx webhooks. When a call comes in, it verifies the signature, persists the call to SQLite, and runs a state machine through discrete phases: new call, answer, gather menu input, process answer, speak closing, hangup. Each phase waits for confirmation before moving forward. Commands are tracked for idempotency; the same command ID never runs twice.
Gate 0
Environment Setup
Python 3.12, UV, Telnyx SDK, Cloudflare tunneling configured and tested
Gate 1
Health Endpoint
FastAPI server returns HTTP 200 with product status from localhost:8788
Gate 2
Configuration & Secrets
Telnyx API credentials loaded from .env, webhook URL updated, tunnel stable
Gate 3
Webhook Verification
Telnyx signature validated using public key, call.initiated events logged and verified
Gate 4
Call Initialization
Incoming call persisted to SQLite with phase, state, token and event tracking
Gate 5
Voice Playback
Greeting audio plays, gather command returns DTMF digits to webhook
Gate 6
Menu State Machine
Menu prompts gather keypad input, call controller routes to service offerings
Gate 7
Call Completion
Closing prompt plays, hangup command received, call marked ended with full audit trail
Architecture
FastAPI server receiving signed webhooks from Telnyx Voice API, persisting call state to SQLite, managing a state machine for call phases, sending voice commands (answer, gather, speak) back to Telnyx, and tracking all activity in an audit log. Cloudflare Tunnels expose the local server to Telnyx webhook delivery.
FastAPI Server
HTTP webhook handler, health endpoint, settings validation, and request routing.
Receives inbound webhooks from Telnyx with signature verification.
Telnyx Voice API
Manages phone connection, audio playback, DTMF detection, and call lifecycle.
Webhook v2 API, bidirectional HTTP control with deduplication window.
SQLite Database
Persists call state, events, commands, and activity audit log across requests.
Pydantic models for Payload and Event, SDK signature verification.
Call Engine
State machine: processes events through phone phases (new, answer, menu, closing, ended).
One worker loop, explicit phase transitions, recovery for interrupted commands.
Telnyx Command Layer
Async HTTP client for voice commands: speak, gather, answer, hangup.
Retry logic, timeout handling, command ID tracking for deduplication.
Business Logic
Knowledge base: shop hours, diagnostic fee, service offerings. No LLM or RAG.
Lookup tables only; decisions are rule-based policy answers.
Why state machines for phone
Phone calls are inherently stateful. A call arrives, you answer it, play a prompt, gather DTMF, respond to input, then hang up. Without an explicit state machine and persisted call record, you end up with callback soup: nested async handlers, race conditions on incoming events, and no way to resume if the server restarts.
Deskline uses discrete call phases stored in SQLite. Every webhook reads the current phase, updates call state, and queues the next command. If a webhook arrives out of order or Telnyx times out, the call record already exists; restart or retry safely.
What it teaches
Bidirectional phone control
FastAPI receives call events from Telnyx, sends commands back via HTTP. Telnyx handles the actual call connection, audio, and DTMF detection. Your Python code decides the workflow.
Stateful call tracking
SQLite stores call phase, current menu token, DTMF input, and full activity log. Each webhook updates a persisted call record, not local variables. Restart at any point.
Webhook signature verification
Every webhook from Telnyx is cryptographically signed. Verify the signature using the account public key before trusting event data.
Command idempotency
Telnyx has a 60-second deduplication window. Use unique command IDs and track command status in SQLite. Retry is safe; no double-charging or double-speaking.
Deterministic testing without calls
Rehearsal mode: press DTMF locally, engine processes events to completion without touching Telnyx. Full workflow test without call credit.