Twilio Voice Twiml

by twilio8aba46fb65dcNo licenseListed Oct 8, 2026Updated Oct 8, 2026

Build voice call logic using TwiML (Twilio Markup Language). Covers the core verbs (Say, Play, Gather, Dial, Record, Conference), generating TwiML with Python and Node.js SDKs, and a complete inbound call IVR example. Use this skill to define call behavior for inbound or outbound calls.

Instructions only

Overview

TwiML is XML that Twilio executes during a call. Your server returns a TwiML document in response to a Twilio webhook POST, and Twilio executes it.

Caller → Twilio → POST to your webhook → Your server returns TwiML → Twilio executes it

Prerequisites

  • Twilio account with a voice-capable phone number — New to Twilio? See twilio-account-setup
  • Webhook endpoint returning TwiML with Content-Type: text/xml
  • SDK (for programmatic generation): pip install twilio / npm install twilio

Quickstart

A minimal inbound call handler that greets the caller and presents a menu:

Python (Flask)

python
from flask import Flask, requestfrom twilio.twiml.voice_response import VoiceResponse
app = Flask(__name__)
@app.route("/voice", methods=["POST"])def handle_call():    response = VoiceResponse()    gather = response.gather(num_digits=1, action="/menu-choice")    gather.say("Welcome to Acme. Press 1 for sales, 2 for support.")    response.redirect("/voice")  # Loop if no input    return str(response)
@app.route("/menu-choice", methods=["POST"])def menu_choice():    digit = request.form.get("Digits")    response = VoiceResponse()    if digit == "1":        response.dial("+15551234567")    elif digit == "2":        response.say("Connecting to support.")        response.dial("+15559876543")    else:        response.say("Invalid option.")        response.redirect("/voice")    return str(response)

Node.js (Express)

node
const { VoiceResponse } = require("twilio").twiml;
app.post("/voice", (req, res) => {    const response = new VoiceResponse();    const gather = response.gather({ numDigits: 1, action: "/menu-choice" });    gather.say("Welcome. Press 1 for sales, 2 for support.");    response.redirect("/voice");    res.type("text/xml").send(response.toString());});
app.post("/menu-choice", (req, res) => {    const digit = req.body.Digits;    const response = new VoiceResponse();    if (digit === "1") response.dial("+15551234567");    else response.say("Invalid option.").redirect("/voice");    res.type("text/xml").send(response.toString());});

Core Verbs

Say — Text-to-speech

Python

python
from twilio.twiml.voice_response import VoiceResponse
response = VoiceResponse()response.say("Your appointment is confirmed.", voice="alice", language="en-US")

Node.js

node
const { VoiceResponse } = require("twilio").twiml;const response = new VoiceResponse();response.say({ voice: "alice", language: "en-US" }, "Your appointment is confirmed.");

Voices: alice (default), man, woman, or Polly/Google TTS (e.g. Polly.Joanna).

Gather — Collect keypad input or speech

Python

python
response = VoiceResponse()gather = response.gather(num_digits=1, action="/handle-input", method="POST")gather.say("Press 1 for sales, press 2 for support.")response.say("We did not receive your input.")  # Fallback if no input

Node.js

node
const gather = response.gather({ numDigits: 1, action: "/handle-input", method: "POST" });gather.say("Press 1 for sales, press 2 for support.");response.say("We did not receive your input.");

Twilio POSTs collected digits to action as Digits parameter.

Play — Play an audio file

Python

python
response = VoiceResponse()response.play("https://example.com/audio/greeting.mp3")

Node.js

node
const response = new VoiceResponse();response.play("https://example.com/audio/greeting.mp3");

Supported formats: MP3, WAV. URL must be publicly accessible.

Dial — Connect to another number

Python

python
from twilio.twiml.voice_response import Dial
response = VoiceResponse()dial = Dial(action="/dial-complete")dial.number("+15558675310")response.append(dial)

Node.js

node
const dial = response.dial({ action: "/dial-complete" });dial.number("+15558675310");

Record — Capture caller audio

Python

python
response = VoiceResponse()response.say("Leave a message after the beep.")response.record(    action="/recording-complete",    max_length=60,    transcribe=True,    transcribe_callback="/transcription-ready")

Node.js

node
const response = new VoiceResponse();response.say("Leave a message after the beep.");response.record({    action: "/recording-complete",    maxLength: 60,    transcribe: true,    transcribeCallback: "/transcription-ready",});

Voicemail — Record a message when no one answers

Use <Dial> with action URL + <Record> in the action handler. When the dial times out or the callee is busy, the action URL serves TwiML with <Record>.

Python

python
# Primary TwiML — try to connect the callresponse = VoiceResponse()dial = Dial(action="/voicemail", timeout=20)  # 20 seconds before voicemaildial.number("+15558675310")response.append(dial)
# /voicemail handler — plays if no answerdef voicemail_handler(request):    response = VoiceResponse()    response.say("We missed your call. Please leave a message after the beep.")    response.record(        action="/recording-complete",        max_length=120,        transcribe=True,        transcribe_callback="/transcription-ready",        play_beep=True    )    response.say("We didn't receive a recording. Goodbye.")    return str(response)

Node.js

node
// Primary TwiML — try to connect the callconst response = new VoiceResponse();const dial = response.dial({ action: "/voicemail", timeout: 20 });dial.number("+15558675310");
// /voicemail handler — plays if no answerapp.post("/voicemail", (req, res) => {    const response = new VoiceResponse();    response.say("We missed your call. Please leave a message after the beep.");    response.record({        action: "/recording-complete",        maxLength: 120,        transcribe: true,        transcribeCallback: "/transcription-ready",        playBeep: true,    });    response.say("We didn't receive a recording. Goodbye.");    res.type("text/xml").send(response.toString());});

Important: <Record> captures the caller only (voicemail-style). It is NOT for recording two-party calls — see twilio-call-recordings for that.

Conference — Multi-party calls

Python

python
response = VoiceResponse()dial = response.dial()dial.conference(    "Daily Standup",    start_conference_on_enter=True,    end_conference_on_exit=True)

Node.js

node
const response = new VoiceResponse();const dial = response.dial();dial.conference("Daily Standup", {    startConferenceOnEnter: true,    endConferenceOnExit: true,});

Pay — PCI-compliant payment collection

Critical warnings:

  • Pay Connectors are Console-only — there is no REST API to create or manage connectors. Set up in Console > Voice > Pay Connectors before coding.
  • PCI Mode is IRREVERSIBLE once enabled on an account. Use a dedicated sub-account for payment calls.

Python

python
response = VoiceResponse()response.say("We'll now collect your payment.")pay = Pay(    payment_connector="stripe_connector",  # Name from Console setup    charge_amount="49.99",    currency="usd",    action="/payment-complete",    status_callback="/payment-status")response.append(pay)

Node.js

node
const response = new VoiceResponse();response.say("We'll now collect your payment.");response.pay({    paymentConnector: "stripe_connector",    chargeAmount: "49.99",    currency: "usd",    action: "/payment-complete",    statusCallback: "/payment-status",});

Supported processors: Stripe, Braintree, CardConnect. Card data routes directly to the processor — never touches your server.


Production Deployment

Webhook Hosting

For production, do NOT use ngrok. Deploy your TwiML server with HTTPS:

  • Requirement: Public HTTPS URL, responds within 15 seconds, returns Content-Type: text/xml
  • Options: Cloud Run, AWS Lambda + API Gateway, Railway, Render — any service with TLS and auto-scaling
  • Fallback URL: Configure in Console (Phone Numbers > Active Numbers > select number) for when your primary server is unreachable

State Between TwiML Requests

Each webhook request is stateless. To maintain conversation state across interactions:

  • URL query params: Pass state in action URLs — /next-step?language=es&dept=sales
  • Session store: Use Redis or a database keyed by CallSid
  • Do NOT use in-memory state — your server may scale to multiple instances

Monitoring

  • Status callbacks: Track call lifecycle events (statusCallback on the call or number config)
  • Voice Insights: Automatic quality metrics per call (Console > Monitor > Insights)
  • Debugger: Console > Monitor > Errors for TwiML parsing failures and webhook timeouts
  • Fallback URLs: Always configure a fallback TwiML URL — serves a graceful message if your primary endpoint fails

Webhook Request Parameters

ParameterDescription
CallSidUnique call identifier
FromCaller's number
ToCalled number
CallStatusCurrent status
Directioninbound or outbound-api

CANNOT

  • Cannot return TwiML without correct content type — Must use Content-Type: text/xml
  • Cannot exceed 15-second webhook response time — Twilio times out and falls back
  • Cannot exceed 4,096 characters in <Say> verb — Split longer text across multiple <Say> elements
  • Cannot create Pay Connectors via API — Pay Connectors are Console-only (Console > Voice > Pay Connectors). No REST API exists for connector management.
  • Cannot reverse PCI Mode — Once enabled on an account, PCI Mode is permanent and account-wide. Use a dedicated sub-account for payment calls.
  • Cannot use <Record> for two-party call recording — <Record> captures the caller only (voicemail-style). For dual-channel recording of both parties, use record=True on calls.create() or the Recordings API.

Next Steps

  • Place outbound calls (AMD, conferencing): twilio-voice-outbound-calls
  • AI voice agents with real-time speech/LLM: twilio-voice-conversation-relay

Source and attribution

Source:twilio/aiinskills/twilio/twilio-voice-twimlat commit8aba46f

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

More from twilio/ai

Twilio Isv Sms Best Practices

twilio

Best practices for ISVs (Independent Software Vendors) building SMS features into multi-tenant SaaS platforms using Twilio. Covers customer onboarding for A2P and toll-free compliance, subaccount architecture, sender management, billing patterns, and common ISV pitfalls. Use this when building SMS capabilities that your customers will use to message their end users.

Awaiting classificationOct 8, 2026

Twilio Security Compliance Hipaa

twilio

Configure Twilio accounts for HIPAA compliance. Covers BAA requirements, HIPAA Project designation (self-service and support), eligible services list, per-product requirements (Voice, SMS, ConversationRelay, Conversation Intelligence, Flex, Verify), message redaction, and what is NOT eligible. Use this skill when developers are building healthcare workflows on Twilio.

Awaiting classificationOct 8, 2026

Twilio Reliability Patterns

twilio

Handle rate limits, retries, and failures when building on Twilio at scale. Covers 429 exponential backoff with jitter, per-number throughput limits, StatusCallback resilience, thin-receiver pattern, and fallback chains. Use this skill whenever sending messages or making calls at volume, or when building production-grade Twilio integrations.

Awaiting classificationOct 8, 2026

Twilio Organizations Setup

twilio

Set up and manage Twilio Organizations for centralized account and user governance. Covers the Organization > Account > Subaccount hierarchy, roles (Owner/Admin/Standard), managed vs independent accounts, domain registration, SSO enforcement, SCIM provisioning, and Organization merging. Use this skill when managing multiple Twilio accounts or users across teams.

Awaiting classificationOct 8, 2026

Twilio Numbers Senders

twilio

Choose the right Twilio number type and sender BEFORE building. Covers phone numbers (local, toll-free, short code, mobile), alphanumeric sender IDs, WhatsApp senders, RCS agents, international availability, and regulatory bundles. Each number type has its own compliance program — choosing wrong means rebuilding. Use this skill first.

Awaiting classificationOct 8, 2026

Twilio Notifications Alerts Advisor

twilio

Planning skill for transactional notifications, alerts, and reminders. Qualifies the developer's needs across urgency, channel selection, delivery confirmation, and fallback patterns to recommend the right Twilio notification architecture. Handles both "send shipping updates to customers" and "build a multi-channel alert system with delivery confirmation and fallback."

Awaiting classificationOct 8, 2026