Back to Portfolio

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.

Project SetupFastAPI ServerWebhook VerificationSQLite State MachineVoice CommandsCall Controller LogicLive Acceptance Testing

Status: Complete Build

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:

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.

Four tables: calls (state machine), events (verified), commands (idempotent), activity (readable).

Settings & Configuration

Loads Telnyx credentials, webhook URL, voice settings, and business policies from .env.

Dataclass with validation, from_env() factory for safe secret handling.

Webhook Reader

Parses Telnyx payloads, verifies signatures, handles event deduplication.

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.

Skills in focus

PythonFastAPIAsync/AwaitSQLitePydantic ValidationWebhooksState MachinesTelnyx Voice APISignature VerificationIdempotency KeysDeterministic TestingError Recovery