DEV Community

Harpreet Singh Seehra
Harpreet Singh Seehra

Posted on

Eliminate Dead Air in AI Voice Assistants with Filler Messages — Telnyx AI Assistant Webhook Demo

When an AI voice assistant calls a sync webhook tool — to look up an order, query a database, check inventory — the caller hears silence. Five seconds of dead air on a phone call feels like thirty. Hang up. Call again. Complaint filed.

Filler messages solve this. Configured per-tool in Telnyx Mission Control, they let the AI Assistant speak scripted phrases while waiting for the webhook to respond: "Let me look that up for you." at 0 seconds, "Still working on this, one moment please." at 5 seconds, "Almost there, thanks for your patience." at 15 seconds. The caller stays engaged. The webhook takes the time it needs. No dead air.

This walkthrough builds a complete demo: a Flask webhook server with a live split-screen dashboard that shows the webhook call, the filler messages firing, and the countdown in real time. 124 lines of Python, one AI Assistant, one phone number, one dashboard — and zero silence.

What You'll Build

A webhook server that an AI Assistant calls when a caller asks about an order:

  • Caller dials the AI Assistant — a voice AI agent configured in Mission Control
  • Caller asks a question — "What's the status of my order 12345?"
  • AI Assistant calls the webhook — a sync tool call to /webhook/order-status
  • Webhook delays — intentionally sleeps for 12 seconds (configurable) to simulate a slow backend
  • Filler messages fire — the AI Assistant speaks scripted phrases at 0s, 5s, and 15s while the webhook is processing
  • Webhook responds — returns mock order status as JSON
  • AI Assistant reads the result — "Your order 12345 has shipped via FedEx, tracking number FX-98765, estimated delivery July 20th."
  • Dashboard shows everything — a split-screen browser UI with the call timeline on the left and server logs on the right, updated in real time via Server-Sent Events

Why Filler Messages Matter

Every voice AI developer hits the same problem: sync webhook tool calls take time. A database query takes 2 seconds. A third-party API takes 5 seconds. A complex lookup takes 10 seconds. On a phone call, that's dead air — and dead air is the enemy of conversation.

Without filler messages, developers build workarounds:

  • Background music — doesn't tell the caller what's happening
  • "Please hold" TTS — fires once, then silence resumes
  • Async tools — more complex to build, don't work for all use cases
  • Faster webhooks — not always possible when you depend on external systems

Filler messages are the carrier-grade solution: the AI Assistant speaks configurable phrases at configurable intervals while the webhook processes. The caller knows the system is working. The developer doesn't have to build a workaround.

Prerequisites

The Architecture

  Caller dials AI Assistant
        │
        ▼
  ┌──────────────────────┐
  │ Telnyx AI Assistant   │
  │ (Mission Control)     │
  └────────┬─────────────┘
           │ sync tool call
           ▼
  ┌──────────────────────┐    SSE     ┌──────────────────┐
  │ Flask Webhook Server  │──────────►│ Browser Dashboard  │
  │ POST /webhook/        │           │ Split-screen UI    │
  │   order-status        │           │ (timeline + logs)  │
  └──────────────────────┘           └──────────────────┘
           │
    delays N seconds,
    then returns mock
    order status
Enter fullscreen mode Exit fullscreen mode

Step 1: Clone and Configure

git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/ai-assistant-filler-messages-demo-python
cp .env.example .env
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Edit .env with your credentials:

TELNYX_API_KEY=your_telnyx_api_key_here
WEBHOOK_DELAY_SECONDS=12
PORT=5000
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up the AI Assistant

The repo includes a setup.py script that creates the AI Assistant, adds the webhook tool with filler messages, and assigns your phone number — all via the Telnyx API.

ngrok http 5000
python setup.py https://abc123.ngrok.io
Enter fullscreen mode Exit fullscreen mode

The script will:

  1. List your active phone numbers — pick one
  2. Create an AI Assistant named "Filler Messages Demo" with Claude Haiku 4.5
  3. Add a sync webhook tool (check_order_status) pointing to your ngrok URL
  4. Configure the three filler messages on the tool
  5. Assign your phone number to the assistant's backing TeXML app

Important: The API sets the filler message values, but playback on calls requires configuring them through the Mission Control UI. After running setup.py, open the tool's Filler Messages tab in Mission Control and verify the three messages are present.

Step 3: Understand the Code

Everything lives in app.py (124 lines). Here's what each piece does.

The Filler Message Configuration

FILLER_CONFIG = [
    {"type": "request_start", "content": "Let me look that up for you.", "timing_ms": 0},
    {"type": "request_response_delayed", "content": "Still working on this, one moment please.", "timing_ms": 5000},
    {"type": "request_response_delayed", "content": "Almost there, thanks for your patience.", "timing_ms": 15000},
]
Enter fullscreen mode Exit fullscreen mode

Two types:

  • request_start — fires immediately when the webhook tool is called
  • request_response_delayed — fires after timing_ms milliseconds if the webhook hasn't responded yet

The Webhook Endpoint

@app.route("/webhook/order-status", methods=["POST"])
def webhook_order_status():
    body = request.get_json(silent=True) or {}
    order_id = body.get("order_id", "unknown")

    broadcast("tool_call_received", {"order_id": order_id, "request_body": body, "delay_seconds": WEBHOOK_DELAY_SECONDS})

    for filler in FILLER_CONFIG:
        if filler.get("timing_ms", 0) / 1000 < WEBHOOK_DELAY_SECONDS:
            broadcast("filler_message", {"content": filler["content"], "filler_type": filler["type"], "timing_ms": filler.get("timing_ms", 0)})

    for elapsed in range(1, WEBHOOK_DELAY_SECONDS + 1):
        time.sleep(1)
        broadcast("countdown", {"elapsed": elapsed, "total": WEBHOOK_DELAY_SECONDS, "remaining": WEBHOOK_DELAY_SECONDS - elapsed})

    order = MOCK_ORDERS.get(order_id, {"order_id": order_id, "status": "not_found", "error": "Order not found"})
    return jsonify({"result": order}), 200
Enter fullscreen mode Exit fullscreen mode

The webhook broadcasts four event types to the dashboard: tool_call_received, filler_message, countdown, and response_sent.

The SSE Dashboard

Real-time updates via Server-Sent Events. Each connected browser gets a queue, and the broadcast() function pushes events to all clients:

def broadcast(event_type, data):
    payload = json.dumps({"type": event_type, "timestamp": time.time(), **data})
    msg = f"event: {event_type}\ndata: {payload}\n\n"
    dead = []
    with clients_lock:
        for q in clients:
            try:
                q.put_nowait(msg)
            except queue.Full:
                dead.append(q)
        for q in dead:
            clients.remove(q)
Enter fullscreen mode Exit fullscreen mode

Step 4: Run the Demo

python app.py
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:5000 to see the split-screen dashboard. Call your AI Assistant's phone number. Ask:

"What's the status of my order 12345?"

Watch the dashboard and listen to the call:

  1. 0s — the AI Assistant says "Let me look that up for you." and calls the webhook
  2. 5s — the AI Assistant says "Still working on this, one moment please."
  3. 12s — the webhook responds with the order status
  4. After response — the AI Assistant reads back the order details

Customizing the Filler Messages

Different timings and content produce very different caller experiences:

Add a 10-second filler:

FILLER_CONFIG = [
    {"type": "request_start", "content": "Let me look that up for you.", "timing_ms": 0},
    {"type": "request_response_delayed", "content": "Still working on this, one moment please.", "timing_ms": 5000},
    {"type": "request_response_delayed", "content": "I'm checking with the shipping carrier now.", "timing_ms": 10000},
    {"type": "request_response_delayed", "content": "Almost there, thanks for your patience.", "timing_ms": 15000},
]
Enter fullscreen mode Exit fullscreen mode

Use context-aware fillers for different tools:

For a payment processing tool:

{"type": "request_start", "content": "Let me process that payment for you.", "timing_ms": 0}
{"type": "request_response_delayed", "content": "I'm confirming the transaction with your bank.", "timing_ms": 5000}
Enter fullscreen mode Exit fullscreen mode

Going to Production

  • Real data source — replace MOCK_ORDERS with actual database or API queries
  • Remove artificial delay — the delay exists only for demo purposes; real webhooks should respond as fast as possible
  • Webhook verification — validate Telnyx webhook signatures
  • Timeout alignment — set the tool timeout_ms (default 5000ms) to at least 30000ms

Frequently Asked Questions

What are AI Assistant filler messages?
Filler messages are scripted phrases the AI Assistant speaks while waiting for a sync webhook tool to respond. They eliminate dead air on the call and keep the caller informed. They're configured per-tool in Mission Control.

How are filler messages different from TTS hold music?
Filler messages are spoken by the AI Assistant in its own voice, as part of the conversation. Hold music is audio played while the call is on hold. Filler messages feel like the assistant is still there and working on your request.

What happens if the webhook responds before the first filler message fires?
If the webhook responds before the first request_response_delayed message's timing_ms, that message doesn't fire. The request_start message always fires immediately when the tool is called.

How do I configure filler messages?
Filler messages are configured per-tool in Mission Control. Open the AI Assistant, edit the webhook tool, and go to the Filler Messages tab. You can also set them via the API (setup.py does this), but playback on calls requires the Mission Control UI configuration.

Resources

Top comments (0)