Business phone numbers and phone menus for the United States and Canada. FRES(510) 288-6288[email protected]Sign in

Home › Twilio › Production checklist

Twilio

The Twilio production checklist the tutorials skip.

Every Twilio voice tutorial ends when the phone rings. That is where the real work starts: a phone menu is a public endpoint that spends money per minute, records people, and fails silently. This is the list we would want done before a real customer dials the number, with Twilio’s own documentation cited for each item and code where code is the clearest explanation.

Last checked 26 October 2026 · 38 cited sources · 32 min read

A production checklist for a voice app

What this list is for

The application in mind is the ordinary one: a business number that answers with a greeting, offers a short menu, forwards the caller to somebody’s mobile, and takes a voicemail when nobody answers. On Twilio Programmable Voice that is a few webhooks returning TwiML. It works in an afternoon, which is the problem: it also looks finished in an afternoon.

What separates that demo from something you can put on a van is unglamorous. Who else can call your webhooks? What does a caller hear when your server is down? Who finds out? What stops a compromised credential or a fraudulent forwarding rule from running up a five-figure bill over a weekend? Where do the voicemails go, and when are they deleted? This page works through those questions in the order we would tackle them.

Everything Twilio-specific below is cited to Twilio’s documentation, its price pages or its helper-library source, as read on 26 October 2026. Where the docs are silent we say so rather than fill the gap. Opinions are labelled. The code is Node.js with the official twilio package (6.x) and, where it helps, Python with twilio 9.x; both are sketches to adapt, not drop-in services.

The checklist

The table is the summary; each row links to its explanation. "If you skip it" is what you get by default, as far as the documentation tells us.

#ItemDone whenIf you skip it
1Validate X-Twilio-SignatureEvery route Twilio calls, including status and recording callbacks, rejects unsigned requests with the official validatorAnyone who learns a URL can drive your menu, forge call logs or trigger your suspend handler
2Fallback URLvoice_fallback_url points at static TwiML on separate infrastructure that still forwards and takes a messageA server outage means calls fail with nothing to fall back to
3Timeouts and retriesVoice webhooks answer in well under 15 seconds; slow work is moved off the requestTwilio retries once, and only on connect failures, then gives up
4Status callbacks and call logEvery call’s final status and every forward’s DialCallStatus lands in your own databaseYou depend on the Console, whose Debugger search covers 30 days
5Alarms and error webhookAn alarm on any error (5-minute window) emails a human and posts to your pager webhookErrors accumulate in the Debugger and nobody is told
6Voice InsightsSomeone reviews the dashboards on a schedule; Advanced Features on if you need per-call metrics through the API"The line sounds bad" complaints with no data, and 30 days of history at most
7Geographic permissionsOnly the countries you forward to are enabled; high-risk ranges stay offA compromised account or a forward-to-any-number bug can call premium-rate ranges
8Usage triggersA daily spend trigger per subaccount that pages you and suspends the subaccount automaticallyNo cap: usage runs until the balance goes negative
9API keys, not the auth tokenApps use Standard or Restricted API keys; the auth token is held only where signatures are validated; you have rehearsed rotationOne leaked secret is full control of the account
10Subaccount per customerEach customer’s numbers, usage and recordings live in their own subaccountOne customer’s fraud or deletion request touches everybody
11Recording retention and deletionVoicemails are copied out (or not), then deleted on a written scheduleRecordings accumulate and are billed for storage every month
12Recording encryptionDecided, and if on, the private key is backed up and access-controlledRecordings readable by anyone with account access
13Canadian callers’ dataPrivacy notice says data may be processed outside Canada; Québec assessment written if you serve QuébecA gap in your PIPEDA and Law 25 obligations
14HIPAA and PCIBAA signed on an eligible edition before any PHI; PCI Mode understood before enablingPHI on an account without a BAA
15Rate limits and retriesREST calls back off on 429; handlers are idempotentBursts fail; retried callbacks create duplicate rows
16TestingSigned fake webhooks in CI; test credentials for REST paths; a staging number for the restValidation switched off in tests and, eventually, in production
17Synthetic test callsA scheduled real call walks the menu and alerts when a step failsYou learn about a broken menu from a customer
18Cost modelA per-call cost that counts both legs of a forward, TTS, recording and storageThe bill is a surprise, usually about forwarding

Scroll the table sideways to see every column.

1. Validate the signature on every webhook

Your voice URL is a public HTTPS endpoint. Twilio does not call it from a fixed set of addresses: the docs say Twilio "not having a fixed range of IP addresses that issue webhooks",1 so an IP allow-list is not the control. The control is the signature. On each request Twilio computes an HMAC-SHA1 "using your account auth token as the secret key" over the webhook URL with the POST parameters "sorted alphabetically" and appended, and sends it in X-Twilio-Signature.1

Twilio is blunt about implementation: "Twilio recommends using the provided signature validation library from a Twilio SDK" and "Don't implement your own signature validation."1 In Node, the twilio.webhook() Express middleware does it. Read from the library source, its behaviour is: validation is on by default; a request with no signature header gets a 400; a request whose signature does not match gets "a text body and a 403"; and if no auth token is configured it returns a 500 rather than letting the request through.2 It reads the token from TWILIO_AUTH_TOKEN unless you pass one.2

// npm install express twilio   (twilio-node 6.x)
const express = require('express');
const twilio = require('twilio');
const { VoiceResponse } = twilio.twiml;

const app = express();
app.use(express.urlencoded({ extended: false })); // the middleware needs the parsed form body

// Tell the validator the URL exactly as Twilio has it configured. Behind a proxy or
// load balancer that terminates TLS, your app sees http:// and an internal host name,
// and every signature fails unless you pin these.
const twilioOnly = twilio.webhook({ protocol: 'https', host: 'ivr.example.com' });

app.post('/voice', twilioOnly, (req, res) => {
  const vr = new VoiceResponse();
  const menu = vr.gather({ numDigits: 1, timeout: 6, action: '/menu' });
  menu.play('https://cdn.example.com/audio/greeting.mp3');
  vr.redirect('/voicemail');            // no key pressed: go to voicemail
  res.type('text/xml').send(vr.toString());
});

// The same middleware goes on EVERY route Twilio calls:
// /menu, /dial-result, /voicemail, /events/call-status, /events/recording, /usage-alarm ...

In Python the equivalent is RequestValidator, whose method signature is validate(uri, params, signature) and which accepts Flask’s multi-value form directly.3

# pip install flask twilio   (twilio-python 9.x)
import os
from functools import wraps
from flask import Flask, request, abort
from twilio.request_validator import RequestValidator

app = Flask(__name__)
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
PUBLIC_BASE = "https://ivr.example.com"   # scheme + host exactly as configured in Twilio

def twilio_only(view):
    @wraps(view)
    def wrapped(*args, **kwargs):
        qs = request.query_string.decode()          # raw, still percent-encoded
        url = PUBLIC_BASE + request.path + ("?" + qs if qs else "")
        signature = request.headers.get("X-Twilio-Signature", "")
        if not validator.validate(url, request.form, signature):
            abort(403)
        return view(*args, **kwargs)
    return wrapped

The ways this goes wrong in production are all about the URL, because the signature covers it:

  • TLS terminated upstream. Twilio’s own tutorial warns that validation fails when the configured URL is https:// but SSL is terminated before your app, because the URL your app sees is not the one Twilio requested.4 Pin protocol and host (or pass the full url option) as above.2
  • Re-encoded query strings. Use "the exact URL from Twilio including any URL-encoded characters"; "If you decode or re-encode the URL, signature validation fails."1 That is why the Python sketch uses the raw query string.
  • Connection overrides. If you add #rt=...-style overrides to a webhook URL (next section), omit that fragment when validating.5
  • Ports. The Node validator already tries the URL with and without the port, "since that is the string Twilio signed".2 Do not add your own port juggling on top.
  • JSON bodies. Voice webhooks are form-encoded, but if a product sends JSON, Twilio adds a bodySHA256 query parameter and the SDK’s validateRequestWithBody checks the body hash too.1

Two more layers are cheap. Twilio "supports HTTP Basic and Digest Authentication" on webhook URLs,1 which gives you a second, independent secret. And Twilio "won’t connect to an HTTPS URL with a self-signed certificate",1 so use a real certificate from the start rather than discovering this on launch day.

Our view: the status-callback, recording-callback and usage-trigger routes matter as much as the menu route. They are the ones that write to your database or, in the spend handler below, suspend an account. An unvalidated one is a way for a stranger to forge your records or switch you off.

2–3. When your webhook is down: fallback URLs, timeouts and retries

Every incoming call starts with Twilio fetching TwiML from your voice_url. The phone number also has a voice_fallback_url, which is "The URL that we call when an error occurs retrieving or executing the TwiML requested by url", and a voice_fallback_method of GET or POST.6 Twilio’s troubleshooting page for error 11200 (HTTP retrieval failure) gives the advice that matters: set a fallback URL "ideally hosted on separate infrastructure".7 A fallback on the same server that just failed is decoration.

Know the clock. Twilio’s defaults are a 5,000 ms connect timeout, 15,000 ms read timeout and 15,000 ms total; the default retry count is 1 and the default retry policy is ct, meaning Twilio retries "only on TCP connect or TLS handshake failures".5 A slow 500 is not retried by default. For voice there is a ceiling on top: Twilio "enforces a hard 15-second timeout on all call-related HTTP requests", which overrides other settings.5 The 11200 page lists "Your server takes too long to respond" among the causes and recommends responding immediately when processing is slow.7 A caller is standing there in silence while you think; our view is that a menu webhook should answer in well under a second, with database writes and CRM lookups moved to a queue.

You can tune this per URL with a fragment, for example https://example.com/foo#ct=1000&rt=5000&rp=ct,rt; options include ct, rt, tt, rc (0 to 5), rp (4xx, 5xx, ct, rt, all) and e for the Twilio edge location, and they work on voice webhooks subject to the 15-second limit.5 Shortening rt on the primary URL means a hung server fails over to the fallback sooner instead of burning the whole 15 seconds. Our view: retrying on 5xx is only safe if the handler is idempotent (see rate limits).

What the fallback should do. The simplest robust fallback is a static TwiML file on a different provider (object storage or a CDN), fetched with GET because static hosts generally serve GET only. It should do the one thing that matters, which is reach a human or take a message, without any of your application logic:

<?xml version="1.0" encoding="UTF-8"?>
<!-- https://static.example.net/ivr/fallback.xml -->
<Response>
  <Say>Thanks for calling. Connecting you now.</Say>
  <Dial timeout="25">
    <Number>+15145550142</Number>
  </Dial>
  <Say>Sorry, nobody is available. Please leave a message after the tone.</Say>
  <Record maxLength="120" action="https://static.example.net/ivr/fallback-bye.xml" method="GET" />
</Response>

The action on <Record> is not optional here. Without one, "<Record> will request the current document’s URL", which Twilio warns "can lead to unwanted looping behavior".21 Point it at a second static file that says goodbye and hangs up. With no action on <Dial>, Twilio moves on to the next verb when the dialled call ends,9 which is what gets an unanswered forward to the voicemail prompt. The message still lands in the account’s Recordings, so nothing is lost while your server is down.

Set both URLs with the Twilio CLI, whose phone-numbers:update command has --voice-url, --voice-fallback-url and --voice-fallback-method flags:34

twilio phone-numbers:update +15145550199 \
  --voice-url https://ivr.example.com/voice \
  --voice-fallback-url https://static.example.net/ivr/fallback.xml \
  --voice-fallback-method GET

Then test it the only honest way: point voice_url at an address that refuses connections, call the number, and confirm you reach the mobile and then the voicemail prompt.

4. Status callbacks and your own call log

The Console will show you calls, but a production service needs its own record: to answer "did that customer’s call reach anybody on Tuesday?", to bill, and to alert. Three callbacks give you almost everything.

  • The number’s status_callback: "The URL we call using the status_callback_method to send status information to your application."6 For calls you place through the API you choose events with StatusCallbackEvent (initiated, ringing, answered, completed), and "If no event is specified, we send the completed status."8 The payload includes CallSid, From, To, Direction, CallStatus, Timestamp and SequenceNumber; CallDuration is present "only in terminal events".8
  • The <Dial> action URL, requested "after the dialed call ends", with DialCallStatus of completed, answered, busy, no-answer, failed or canceled and the new leg’s DialCallSid.9 This is the event that tells you whether a forward reached a person, which the parent call’s status does not.
  • recordingStatusCallback on <Record>, requested when the recording is available, with events in-progress, completed and absent (default completed).21 That is your "new voicemail" trigger.
// Forward, then log the outcome and fall through to voicemail when nobody answered.
app.post('/menu', twilioOnly, (req, res) => {
  const vr = new VoiceResponse();
  if (req.body.Digits === '1') {
    const dial = vr.dial({ timeout: 20, action: '/dial-result', answerOnBridge: true });
    dial.number('+15145550142');
  } else {
    vr.redirect('/voicemail');
  }
  res.type('text/xml').send(vr.toString());
});

app.post('/dial-result', twilioOnly, async (req, res) => {
  await events.insert({
    callSid: req.body.CallSid, kind: 'dial', status: req.body.DialCallStatus,
    childSid: req.body.DialCallSid || null,
  });
  const vr = new VoiceResponse();
  if (req.body.DialCallStatus !== 'completed') vr.redirect('/voicemail');
  else vr.hangup();
  res.type('text/xml').send(vr.toString());
});

// Final status for every call on the number (set as the number's status_callback).
app.post('/events/call-status', twilioOnly, async (req, res) => {
  const b = req.body;
  // Unique key on (callSid, kind, status) so a retried callback is a no-op.
  await events.insertIgnoreDuplicate({
    callSid: b.CallSid, kind: 'call', status: b.CallStatus, from: b.From, to: b.To,
    durationS: b.CallDuration ? Number(b.CallDuration) : null, at: b.Timestamp || null,
  });
  res.sendStatus(200);
});

Two details from the <Dial> reference matter for a forwarding menu. The default timeout is 30 seconds, and "Twilio always adds a five-second timeout buffer", so a 20-second timeout rings for roughly 25.9 Set it below the time at which the mobile carrier’s own voicemail picks up, or your callers will leave messages in a personal mailbox instead of yours. And answerOnBridge="true" keeps the inbound call ringing until the dialled number answers when <Dial> is the first verb.9

You can attach the status callback to the number with the helper library (client.incomingPhoneNumbers(sid).update({ statusCallback: ..., statusCallbackMethod: 'POST' })) as part of the same provisioning script that sets the voice and fallback URLs, so a newly bought number is never missing it.

5. The Debugger, alarms and the error webhook

Twilio’s Debugger (Console, Monitor > Debugger) lists events with level, error code, source and description; "Warnings mean Twilio could still process the request, and errors mean it couldn’t", and its search reaches "event data from within the last 30 days".10 You can also push errors to your own endpoint: "To configure error webhooks, go to Monitor > Error webhook."10 That is the stream to feed into whatever already pages you.

Alarms add thresholds. They "monitor for specific error codes or error types" (any error, any warning, or both), fire when the count exceeds your threshold over a window of 5 minutes, 15 minutes, 1 hour, 12 hours or 24 hours, and notify within 15 seconds by email, by a POST to your webhook, or both.11 The webhook carries ErrorCode, CurrentValue, TriggerValue, TimePeriod and an IdempotencyToken for de-duplication.11

Two limits are easy to miss and both matter for a multi-customer setup: alarms "are only available in the US1 region", and they "don’t aggregate from subaccounts into parent accounts".11 If you give each customer a subaccount, each subaccount needs its own alarms, created by the same provisioning script that creates the subaccount.

Our view of a sensible starting set: an alarm on error 11200 (webhook retrieval failure7) at a threshold of 1 in 5 minutes, because on a voice line each occurrence is a caller who just hit your fallback; and an "any error" alarm at a modest threshold over an hour to catch the things you did not think of.

6. Voice Insights for call quality

Errors are not the only failure. Calls that connect but sound bad generate complaints and no error codes. Voice Insights "provides call quality analytics and aggregation tools for investigating Twilio calls and conferences", with account-level dashboards covering call volume, length, connectivity and audio quality, and a per-call Call Summary of "call metadata, connection parameters, and quality indicators".12

The REST resources for metrics and events, time-series exploration and event streams sit behind Advanced Features, which "are available at an additional cost to regular call charges";12 the US price page lists Voice Insights Advanced Features at $0.0024 per minute.37 Whatever you enable, "Voice Insights data is available for calls or conferences made within the last 30 days."12

Our view: for a small menu-and-forwarding line, the free dashboards reviewed weekly are enough, plus the Call Summary when a specific complaint comes in. Turn Advanced Features on when you need quality data in your own systems, and export anything you want to keep beyond the 30-day window.

7. Geographic permissions: block toll fraud before it starts

An inbound phone menu seems to have nothing to steal. It does: every forward is an outbound call billed to you. Twilio’s anti-fraud guide says of Programmable Voice that toll-fraud charges "are billed to the owner of the calling number — so if it’s your number, you’re the victim of the fraud", and that "Charges are by the minute, so the longer the duration of each call, the more the fraudster will earn from it."14 The pattern is International Revenue Sharing Fraud, where calls are routed to premium numbers the fraudster profits from; Twilio puts industry-wide losses at "$10 billion" a year.15

The control is Voice Dialing Geographic Permissions, which apply to Programmable Voice and Elastic SIP Trunking: you enable or disable each country and, separately, the high-risk number ranges within it.15 Twilio’s guidance is to "restrict calling only to countries you expect to call, if any, and block all others."14 "Only Owner and Administrator roles can update Geographic Permissions", and a new subaccount "inherits Geo Permissions from the Master Project" unless you turn inheritance off for it.15

For a US or Canadian business forwarding to staff mobiles, that usually means the United States and Canada enabled, high-risk ranges off, and nothing else. We could not find, in the pages we read, a statement of which countries are enabled by default on a new account, so check yours rather than assuming.

The design rule that matters more than the setting

Never <Dial> a number that came from the caller (digits they entered, a "call me back on" field, a number in a CRM that a customer can edit) without checking it against an allow-list of destinations you actually forward to. Geographic permissions are the backstop; the allow-list in your code is the control.

The callerId rules on <Dial> are part of the same hygiene: for a normal call it may be the caller’s or the called number from Twilio’s request, a Twilio number you bought, or a number verified with Twilio.9 Passing the original caller’s number through is what lets staff see who is calling on their mobile.

8. Usage triggers: the nearest thing to a spend cap

In the documentation we read, Twilio describes no hard spending limit on an account. The anti-fraud guide’s closest mechanism is usage triggers,14 which notify rather than block: a UsageTrigger "is a webhook that notifies your application of usage thresholds", evaluated "about once a minute", firing on count, usage or price, recurring daily, monthly or yearly.13 At the other end, Twilio’s fraud-response guide says "If your account balance becomes negative or you have overdue payments, your account will be automatically suspended" and that Twilio "does not often suspend right at zero balance".16 Between those two points, the meter runs.

So build the cap yourself. Twilio’s own example is to "suspend a subaccount in response to a usage trigger being fired for spending more than $30 per day".14 A suspended account "can’t make or receive phone calls",20 which is drastic for a business line, so pick the threshold well above a bad legitimate day and page a human at a lower one.

// Provisioning: two daily price triggers on a customer's subaccount.
const main = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const sub = main.api.v2010.account(customer.subaccountSid);

await sub.usage.triggers.create({
  usageCategory: 'totalprice', triggerBy: 'price', recurring: 'daily',
  triggerValue: '10', callbackUrl: 'https://ivr.example.com/usage-alarm?action=page',
});
await sub.usage.triggers.create({
  usageCategory: 'totalprice', triggerBy: 'price', recurring: 'daily',
  triggerValue: '40', callbackUrl: 'https://ivr.example.com/usage-alarm?action=suspend',
});

// Handler: validated like every other Twilio webhook.
app.post('/usage-alarm', twilioOnly, async (req, res) => {
  const { AccountSid, CurrentValue, TriggerValue, IdempotencyToken } = req.body;
  if (await seen(IdempotencyToken)) return res.sendStatus(200);
  await pager.send('Twilio spend ' + CurrentValue + ' passed ' + TriggerValue + ' on ' + AccountSid);
  if (req.query.action === 'suspend' && AccountSid !== process.env.TWILIO_ACCOUNT_SID) {
    await main.api.v2010.accounts(AccountSid).update({ status: 'suspended' });
  }
  res.sendStatus(200);
});

Notes on that sketch. The parameter names (usageCategory, triggerValue, callbackUrl, triggerBy, recurring) and the callback fields (CurrentValue, TriggerValue, IdempotencyToken) are from Twilio’s UsageTrigger reference, whose examples use totalprice as a category; "A trigger will only fire once during each period", and recurring periods are in GMT.13 The suspend call uses the main account’s credentials deliberately: a Standard API key has access to everything "except for Accounts" and Keys,17 so it cannot suspend anything. The guard stops the handler suspending the main account, which would take every subaccount down with it.20 And the evaluation lag is real: "It can take some amount of time for the data used by usage triggers to be reflected".13 Treat triggers as minutes-late, not instantaneous.

Our view on auto-recharge: it keeps a busy line from being suspended for a zero balance, which you want, but during an attack it also keeps funding the attack. Keep the recharge amount modest and make sure the recharge notification email reaches a person who reads it.

9. API keys, the auth token and rotation

Twilio warns that "Anyone who has your account SID and your auth token can make an API request for your account."14 It calls API keys "the preferred way to authenticate with Twilio’s REST APIs" and offers three kinds: Main ("Full access to all Twilio API resources. Equivalent to using your Account SID and Auth Token"), Standard (everything except the Accounts and Keys resources) and Restricted ("Customized, fine-grained access to specific Twilio API resources").17 "If a key is compromised or no longer used, revoke it".17

In twilio-node, authenticating with a key means passing the key SID and secret plus the account SID: the client throws if the first argument is an SK key SID without an accountSid option.18

const client = twilio(process.env.TWILIO_API_KEY_SID, process.env.TWILIO_API_KEY_SECRET, {
  accountSid: process.env.TWILIO_ACCOUNT_SID,
});

The catch that shapes the whole design: webhook signatures are computed with the auth token,1 so the processes that validate webhooks must hold the auth token no matter how you authenticate REST calls. The split we recommend is to give your web tier the auth token for validation only, give everything that calls the REST API a Standard or Restricted key, and keep a Main key or the auth token for the handful of account-level operations (suspending subaccounts, creating keys).

Rotation. The auth token has a primary and a secondary; promoting the secondary "deletes the current primary Auth Token and promotes the secondary Auth Token to primary".19 Twilio also warns that where the token is hard-coded in Services or Functions (Classic) rather than in a variable, "you must wait for 1 minute for the update of your auth token to propagate", or those calls fail with 403.19 The docs we read do not say how signing behaves during a rotation. Our inference from the signing rule is that once the new token is primary, signatures are computed with it, so the safe order is: create the secondary, make your validators accept either token for the changeover, promote, then drop the old one. Rehearse this on a test account before you need it at 2 a.m. after a leak, and remember that keys minted with a compromised token "should be invalidated or deleted" too.14

10. One subaccount per customer

If you run phone menus for more than one business, subaccounts are the unit of isolation: "Using a subaccount, you can segment each of your customers’ use of Twilio and keep it separate from all the rest", and each can have its own phone numbers, applications and SIP domains.20 A main account can have "up to 1000 subaccounts by default"; all usage is billed to the main account, and if the main account is suspended its subaccounts are too.20

What you get from the split: per-customer usage triggers and suspension (above), per-customer recordings you can delete in one place, per-customer geographic permissions when inheritance is turned off,15 and a clean exit. Closing is final: Twilio "will release all phone numbers assigned to it", "you can’t reopen a closed account", and where the deletion setting is on (the default for accounts created after 5 July 2018), Twilio deletes "all subaccount data 30 days after closure".20

Closing releases the numbers

A customer leaving with their business number must port it out before you close their subaccount. Close first and the number is released.20 Suspend while you wait; do not close.

The costs of the split are the ones already mentioned: alarms do not roll up,11 so every monitor has to be created per subaccount, and your provisioning script becomes the most important piece of operations code you own.

11–12. Voicemail recordings: storage, retention, deletion and encryption

A voicemail is a recording of a member of the public, often with a name, a phone number and the reason they called. Treat it as personal information from the start.

Capture. <Record> defaults to a maxLength of 3,600 seconds (one hour), a timeout of 5 seconds of silence, any key to finish, a beep, and trim-silence.21 An hour is far too long for a voicemail; 120 to 180 seconds is plenty and caps your storage. Twilio’s built-in transcription is "only supported in American English", and a transcribed recording defaults to a two-minute maxLength,21 which rules it out for French-speaking callers in Québec; the US price page lists it at $0.05 per minute.37

Storage and access. Recording is $0.0025 per minute and storage $0.0005 per minute per month on the US price page.37 Neither the price page nor the Recording reference we read states a default retention period for ordinary recordings, and storage is billed monthly, so plan as if they stay until you delete them. Media access is authenticated: "Twilio enforces HTTP Basic Authentication for all media URLs", and Twilio suggests an API key SID and secret as the credentials.22

Deletion. DELETE on a recording removes the media ("The recording media cannot be recovered"), stops storage billing, and returns 204; only completed recordings can be deleted, and "The metadata is preserved for a period of 40 days", visible in the Console and API.22 The Console also has bulk deletion.22 A minimal retention job:

# Python: copy voicemails older than N days to your own store (or not), then delete at Twilio.
import os, datetime as dt
from twilio.rest import Client

client = Client(os.environ["TWILIO_API_KEY_SID"], os.environ["TWILIO_API_KEY_SECRET"],
                os.environ["TWILIO_ACCOUNT_SID"])
cutoff = dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=30)

for rec in client.recordings.list(date_created_before=cutoff, page_size=100):
    if rec.status != "completed":
        continue
    archive_if_required(rec)          # your decision: many businesses should keep nothing
    client.recordings(rec.sid).delete()

Our view: decide the retention period before launch, write it into your privacy notice, and make the job delete by default. A voicemail has done its job once somebody has listened and acted; keeping three years of them is a liability with a monthly invoice. If you need to record live calls rather than voicemail, consent rules vary by state and province; our call recording checklist covers them.

Encryption. Twilio offers Voice Recording Encryption: for each recording it generates a content key, encrypts the audio "with the generated CEK using the AES256-GCM cipher", and wraps that key with your RSA public key; "There is no one at Twilio, including Twilio support, that will be able to decrypt your recordings."23 It is enabled at project or subaccount level with a public key you upload.23 The trade-offs are stated plainly in the same guide: lose the private key and the recordings are unrecoverable, the uploaded public key cannot be retrieved later, and if encryption fails "The recording file is deleted and will not be available for access."23 Encrypted recordings carry encryption_details describing how to decrypt them.22 Our view: worth it where recordings are sensitive and you already run proper key management; not worth it if the private key would end up in the same environment variables as everything else.

13. Canadian callers’ data stored in the United States

Twilio’s data Regions are United States (US1), Ireland (IE1) and Australia (AU1); there is no Canadian Region in that list, not every product is available outside US1, and Twilio says that during the rollout it "does not guarantee that all data will remain within your selected Region".24 For a Canadian business on a default account, assume call records and voicemails are stored in the United States.

That is lawful but carries obligations. Under PIPEDA, the federal privacy commissioner’s guidance is that the Act "does not prohibit organizations in Canada from transferring personal information" for processing; the organization remains responsible, should use contractual means to "provide a comparable level of protection", and should tell people, in plain language, that their information may be processed abroad and "may be accessible to law enforcement and national security authorities of that jurisdiction".25 (That guidance dates from 2009; it is still the commissioner’s published position on the page we read.)

Québec goes further. Under section 17 of its private-sector privacy Act, "Before communicating personal information outside Québec, a person carrying on an enterprise must conduct a privacy impact assessment", weighing sensitivity, purposes, protection measures and the foreign legal framework, and the communication "must be the subject of a written agreement"; the same applies when you entrust a body outside Québec with keeping the information on your behalf.26 Section 8 requires telling people, where applicable, of "the possibility that the information could be communicated outside Québec".26

In practice: write a short assessment of the voicemail and call-log flow, keep Twilio’s terms and data-processing documents with it, put the outside-Canada and outside-Québec sentence in your privacy notice, and keep retention short, which shrinks every other risk. This is a summary of the texts, not legal advice. The site’s Canada page covers the Canadian telephone rules more broadly.

14. HIPAA and PCI, as Twilio documents them

HIPAA. Twilio’s position is that customers subject to HIPAA who will process protected health information must sign a Business Associate Addendum, and that signing one requires the Security Edition or Enterprise Edition.27 Its eligible-services list (last updated 30 June 2026) includes Programmable Voice basics with Call Recordings and Storage, Call Transcription, Voice Insights and others, with asterisked items needing the special handling described in its architecture guide; and it states that covered entities and business associates agree not to use eligible services with PHI "without first entering into a Twilio Business Associate Agreement".28 That architecture guide (dated 11.09.22) requires HIPAA workflows to verify that requests "are indeed coming from Twilio" by checking the signature, and to enforce HTTP basic auth on recording media.29 The current Recording reference repeats that "we require customers to enforce at least HTTP Authentication."22

A clinic’s voicemail ("this is about my test results") is PHI. If your menu serves healthcare, the BAA comes before the first real call, not after.

PCI. If callers will key card numbers, Twilio’s route is the TwiML <Pay> noun with PCI Mode, which "redacts sensitive payment details captured using Twilio Programmable Voice and <Pay>".30 Two facts to know before you click: "Once PCI Mode is enabled, it cannot be disabled for that Account", and PCI voice recordings are "retained for one (1) year from creation, by default" and then deleted.30 The page points to Twilio’s responsibility matrix for who is responsible for what.30 Our view: put payment flows in their own subaccount so PCI Mode’s irreversibility does not spread to lines that never take a card, and never let a caller read a card number to a voicemail.

15. Rate limits and retries

Two different limits apply, and they behave differently.

Calls per second. "Calls are rate limited at the account level by Calls Per Second (CPS)", "By default, each account is granted one CPS for calls created via POST requests to the /Calls endpoint", and calls beyond it "will be queued and will execute at the CPS rate".8 An inbound menu rarely creates calls through the API, but the synthetic probe below and any call-back feature do, and one CPS is shared by all of them.

REST concurrency. Error 20429 is returned "when your account exceeds allowed concurrency to Twilio’s REST API"; those requests "aren’t processed and are safe to retry after backing off", and Twilio recommends exponential backoff and watching the Twilio-Concurrent-Requests response header.31 twilio-node can do the backoff for you: autoRetry: true enables "automatic retry with exponential backoff" on 429, disabled by default, with maxRetries defaulting to 3.18

const client = twilio(process.env.TWILIO_API_KEY_SID, process.env.TWILIO_API_KEY_SECRET, {
  accountSid: process.env.TWILIO_ACCOUNT_SID,
  autoRetry: true,
  maxRetries: 5,
});

Retries in the other direction. Twilio retries your webhooks on connect failures by default, and on more if you set rp.5 Alarm and usage-trigger callbacks carry an IdempotencyToken precisely so you can de-duplicate.1113 Our view: make every handler that writes idempotent (a unique key on call SID plus event, as in the logger above), and keep handlers that only return TwiML free of side effects so a retry is harmless.

16. Testing: test credentials, signed fakes and the CLI

Test credentials "exercise parts of the REST API without incurring charges", covering creating calls and messages, buying numbers and Lookup, with magic numbers such as +15005550006 as a valid From.32 Know what they do not do: "Because no call is placed, Twilio doesn’t request the URL specified in the Url parameter and no TwiML is executed", and test calls "don’t trigger status callbacks".32 They test your REST integration (provisioning, error handling for invalid or blocked numbers), not your menu.

Your TwiML handlers are plain HTTP and are best tested as such. Twilio’s Express tutorial shows switching validation off when NODE_ENV is test.4 We prefer keeping validation on and signing test requests with the library’s exported getExpectedTwilioSignature(authToken, url, params),2 so a broken validator fails CI instead of production:

// test/voice.test.js (supertest + node:test)
const request = require('supertest');
const twilio = require('twilio');
process.env.TWILIO_AUTH_TOKEN = 'test-token-not-a-secret';
const app = require('../app');

test('menu option 1 forwards with a 20 s timeout', async () => {
  const params = { CallSid: 'CA00000000000000000000000000000000', Digits: '1' };
  const url = 'https://ivr.example.com/menu';
  const sig = twilio.getExpectedTwilioSignature(process.env.TWILIO_AUTH_TOKEN, url, params);
  const res = await request(app).post('/menu').set('X-Twilio-Signature', sig).type('form').send(params);
  assert.match(res.text, /<Dial[^>]*timeout="20"/);
});

test('unsigned requests are rejected', async () => {
  const res = await request(app).post('/menu').type('form').send({ Digits: '1' });
  assert.equal(res.status, 400);   // no header: 400; wrong signature: 403
});

The Twilio CLI installs with brew tap twilio/brew && brew install twilio (or npm, which Twilio discourages), and twilio login stores credentials as a named profile, so you can keep staging and production apart on one machine.33 One sharp edge: phone-numbers:update throws "Localhost URLs are not allowed for this operation." if you pass a localhost or 127.0.0.1 URL,34 so local development needs a public tunnel URL. Our view: keep a separate staging number in a separate subaccount, wired to your staging deployment, and never point a production number at a laptop.

17. Synthetic test calls: let a robot call your menu

Signature checks, alarms and logs tell you when something errors. They do not tell you that the greeting file was replaced with silence, that option 2 now goes to a number that was disconnected, or that the menu works but the audio says the office closes at 5 when it now closes at 6. A synthetic probe is a real call, placed on a schedule, that walks the menu and checks what it hears.

Twilio has the pieces. A call created through the API can carry its own inline Twiml instead of fetching a URL, and a SendDigits string of up to 32 keys.8 On the probe leg, <Gather input="speech"> transcribes what it hears into SpeechResult with a Confidence score, and actionOnEmptyResult makes it post even when it hears nothing.35 <Play digits> sends key presses, with w for a half-second pause.36

// probe.js: run from cron, e.g. hourly during business hours.
// The probe calls FROM a separate number you own, used only for probes.
const MENU = '+15145550199', PROBE_FROM = '+15145550177', BASE = 'https://monitor.example.com';

await client.calls.create({
  to: MENU, from: PROBE_FROM, timeLimit: 90,
  statusCallback: BASE + '/probe/status',
  statusCallbackEvent: ['answered', 'completed'],
  twiml: '<Response><Gather input="speech" timeout="8" speechTimeout="2" ' +
         'actionOnEmptyResult="true" action="' + BASE + '/probe/greeting"/></Response>',
});

// Step 1: did we hear the greeting and the voicemail option? Then press 3.
app.post('/probe/greeting', twilioOnly, async (req, res) => {
  const heard = (req.body.SpeechResult || '').toLowerCase();
  const ok = /message|voicemail/.test(heard) && /press (3|three)/.test(heard);
  await probes.step(req.body.CallSid, 'greeting', ok, heard);
  const vr = new VoiceResponse();
  if (!ok) { vr.hangup(); return res.type('text/xml').send(vr.toString()); }
  vr.play({ digits: 'w3' });
  vr.gather({ input: 'speech', timeout: 8, speechTimeout: 2, actionOnEmptyResult: true,
              action: BASE + '/probe/voicemail-prompt' });
  res.type('text/xml').send(vr.toString());
});

// Step 2: did option 3 reach the voicemail prompt? Hang up before the beep.
app.post('/probe/voicemail-prompt', twilioOnly, async (req, res) => {
  const heard = (req.body.SpeechResult || '').toLowerCase();
  await probes.step(req.body.CallSid, 'voicemail', /after the tone|leave a message/.test(heard), heard);
  const vr = new VoiceResponse();
  vr.hangup();
  res.type('text/xml').send(vr.toString());
});

// Alert on: a failed step, a probe with no 'completed' status, or CallStatus busy/failed/no-answer.

Design choices worth stating. The probe walks the voicemail path, not the forwarding path, because a probe that forwards rings a real person every hour; check forwarding through your own /dial-result log instead, where a run of failed or no-answer on one destination is the signal.9 Speech matching is fuzzy by nature (a recogniser may write "three" or "3"), so match loosely and alert on two consecutive failures rather than one. And probe the fallback too: once a week, run the same probe against a staging copy of the number whose voice_url is deliberately broken.

The probe has a cost, worked out in the next section, and it uses the account’s one default CPS when it starts.8 Our view: hourly during business hours is the right default for a small business line; every few minutes is for lines where a missed hour is expensive.

18. Cost modelling, per minute

Twilio voice pricing is per minute per leg, and the leg that surprises people is the forward. A call that comes in on your number and is forwarded with <Dial> becomes two calls: the inbound one, and "the new call leg" that <Dial> creates.9 Our reading of the price page is that the second leg is priced as an outbound call to the destination. The figures below are from Twilio’s US Programmable Voice price page, which states "Pricing current as of August 2026"; the page uses a dollar sign without naming a currency, and we read it as US dollars.37

ItemUS price page (Aug 2026)
Local number$1.15 per month
Toll-free number$2.15 per month
Inbound, local number$0.0085 per minute
Inbound, toll-free number$0.0220 per minute
Outbound to US numbers (the forwarded leg)$0.0140 per minute
Call recording$0.0025 per minute
Recording storage$0.0005 per minute per month
Transcription$0.0500 per minute
Speech recognition (default provider)$0.02 per use
Text-to-speech, basic voicesFree
Text-to-speech, Premium Neural$0.0032 per 100 characters
Voice Insights Advanced Features$0.0024 per minute

Scroll the table sideways to see every column.

Caller-name (CNAM) lookup on the number is "$0.01 per look up" according to the phone-number reference.6 The Canadian price page shows the same figures for local and toll-free numbers, inbound minutes, outbound calls to "United States & Canada" ($0.0140), recording and storage, with Yukon an exception at $0.1525 per minute outbound; it too does not name a currency.38 The US page says volume discounts apply automatically past a monthly-minutes threshold and that "Volume pricing applies to U.S. and Canadian voice minutes".37 The page does not say how call durations are rounded for billing, so the arithmetic below, done on exact minutes, is a floor rather than a quote.

A worked month. A small business on one local number, 600 inbound calls, average 3 minutes on the inbound leg (1,800 minutes). 480 of them are forwarded and talk for 2.5 minutes on the forwarded leg (1,200 minutes); 120 leave a one-minute voicemail.

LineArithmeticMonthly
Number1 × $1.15$1.15
Inbound minutes1,800 × $0.0085$15.30
Forwarded leg1,200 × $0.0140$16.80
Voicemail recording120 × $0.0025$0.30
Voicemail storage (deleted after 30 days)about 120 stored minutes × $0.0005$0.06
Greeting by Premium Neural TTS, 300 characters per call600 × 3 × $0.0032$5.76
Synthetic probe, hourly over 10 business hours on 22 days, about 1 minute each220 × ($0.0140 + $0.0085 + 2 × $0.02)$13.75
Total$53.12

Scroll the table sideways to see every column.

What the model shows. The forwarded leg costs more than the inbound minutes, so the forwarding design (ring timeout, who gets the call) is the cost lever, not the menu. A synthesised greeting is billed on every call, while the same words recorded once and served with <Play> cost nothing per call beyond hosting; our view is that a recorded greeting is also better for callers. Keeping voicemails for a year instead of 30 days changes storage from 6 cents to about 72 cents a month by the end of the year (1,440 stored minutes), which is trivial in money and not trivial in privacy. And monitoring is not free: the probe above is about a quarter of this bill, and probing every 15 minutes around the clock (2,880 probes at about $0.0625) would be about $180 a month, more than the traffic. Size it to what an hour of a broken menu costs you; our missed-call cost calculator is one way to put a number on that.

Two exposures are not in the table because they are not supposed to happen: a forward to a high-cost destination, and an attack. They are what sections 7 and 8 are for.

If you only have a day

Our ordering, by how much damage each gap can do and how cheap it is to close:

  1. Signature validation on every route (an hour, and it closes the widest hole).
  2. Geographic permissions down to the countries you forward to, plus an allow-list in code (minutes).
  3. A daily usage trigger that pages, and one that suspends (an hour).
  4. A static fallback on separate hosting, tested by breaking the primary (an hour).
  5. Alarms on 11200 and on any error, to email and a webhook (minutes).
  6. Status and <Dial> logging into your own database (an afternoon).
  7. A written recording retention period and the job that enforces it (an afternoon).

The rest (API key split, subaccounts, probes, encryption, the Québec assessment) is the second day. All of it is ongoing work; it is the work a hosted phone service does for you, and the honest reason to weigh building against buying before you start. The Twilio guides hub has the other pieces of the build, and our call-flow templates are a useful spec for what the menu itself should do.

Further reading

Longer research pieces on this topic, each with its own source list and a downloadable PDF.

Sources

Every regulatory and pricing claim on this page links to the primary source it came from. Figures were checked on the date shown at the top of the page; rules and prices change, so verify before relying on one.

  1. Twilio Docs, "Webhooks security": HMAC-SHA1 signature over URL and sorted parameters with the auth token, use the SDK validator, exact URL, no fixed IP range, Basic/Digest auth, no self-signed certificates. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/usage/webhooks/webhooks-security
  2. twilio-node source, src/webhooks/webhooks.ts: webhook() middleware options (validate, host, protocol, url), 400/403/500 behaviour, port handling, getExpectedTwilioSignature. main branch; npm release 6.2.0; retrieved 26 Oct 2026
    https://github.com/twilio/twilio-node/blob/main/src/webhooks/webhooks.ts
  3. twilio-python source, twilio/request_validator.py: RequestValidator.validate(uri, params, signature) and Flask MultiDict support. main branch; PyPI release 9.12.0; retrieved 26 Oct 2026
    https://github.com/twilio/twilio-python/blob/main/twilio/request_validator.py
  4. Twilio Docs, "Secure your Express app by validating incoming Twilio requests" (validate option, failure behind TLS termination). Retrieved 26 Oct 2026
    https://www.twilio.com/docs/usage/tutorials/how-to-secure-your-express-app-by-validating-incoming-twilio-requests
  5. Twilio Docs, "Webhooks connection overrides": default timeouts and retry policy, fragment syntax, the 15-second limit on voice requests. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/usage/webhooks/webhooks-connection-overrides
  6. Twilio Docs, IncomingPhoneNumber resource: voice_url, voice_fallback_url, status_callback, CNAM lookup price. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/phone-numbers/api/incomingphonenumber-resource
  7. Twilio Docs, Error 11200 "HTTP retrieval failure": causes and the fallback-URL recommendation. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/api/errors/11200
  8. Twilio Docs, Call resource: StatusCallbackEvent, default completed event, callback parameters, SendDigits, Twiml, CPS queueing. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/voice/api/call-resource
  9. Twilio Docs, TwiML <Dial>: new call leg, action URL, DialCallStatus values, timeout default and five-second buffer, callerId rules. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/voice/twiml/dial
  10. Twilio Docs, "Debugging your application": Debugger fields, error webhook, 30-day search. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/usage/troubleshooting/debugging-your-application
  11. Twilio Docs, Alarms: thresholds, time windows, email and webhook delivery, US1 only, no subaccount aggregation, payload fields. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/usage/troubleshooting/alarms
  12. Twilio Docs, Voice Insights: dashboards, Call Summary, Advanced Features at extra cost, 30-day data window. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/voice/voice-insights
  13. Twilio Docs, UsageTrigger resource: TriggerBy, Recurring, evaluation about once a minute, callback parameters. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/usage/api/usage-trigger
  14. Twilio Docs, "Anti-Fraud Developer’s Guide": restrict calling countries, suspend a subaccount on a $30/day trigger, credential replacement, toll fraud billing. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/usage/anti-fraud-developer-guide
  15. Twilio Docs, "Voice Dialing Geographic Permissions": per-country and high-risk controls, Owner/Administrator only, subaccount inheritance, IRSF. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/voice/tutorials/voice-dialing-geographic-permissions
  16. Twilio Docs, Fraud response guide, "Engage: contact Twilio Support": suspension on negative balance. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/usage/fraud-response-guide/engage
  17. Twilio Docs, API keys: Main, Standard and Restricted key types, revocation. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/iam/api-keys
  18. twilio-node README: subaccount access, autoRetry with exponential backoff on 429 (default 3 retries); BaseTwilio.ts requires accountSid when authenticating with an API key. main branch; retrieved 26 Oct 2026
    https://github.com/twilio/twilio-node/blob/main/README.md
  19. Twilio Docs, AuthToken resource: promoting the secondary token deletes the primary; one-minute propagation warning. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/iam/api/authtoken
  20. Twilio Docs, Subaccounts: per-customer segmentation, 1,000 default limit, billing to the main account, suspend vs close, data deletion 30 days after closure. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/iam/api/subaccounts
  21. Twilio Docs, TwiML <Record>: maxLength, timeout, trim defaults, recordingStatusCallback, transcription limits, looping without action. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/voice/twiml/record
  22. Twilio Docs, Recording resource: HTTP Basic Authentication on media URLs, DELETE semantics, 40-day metadata retention, HIPAA note. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/voice/api/recording
  23. Twilio Docs, "Voice Recording Encryption": AES256-GCM content key wrapped with your RSA public key; Twilio cannot decrypt. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/voice/tutorials/voice-recording-encryption
  24. Twilio Docs, Global infrastructure: Regions US1, IE1 and AU1, and the residency caveat. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/global-infrastructure
  25. Office of the Privacy Commissioner of Canada, "Guidelines for processing personal data across borders" (PIPEDA accountability and notice). Published 27 January 2009; retrieved 26 Oct 2026
    https://www.priv.gc.ca/en/privacy-topics/airports-and-borders/gl_dab_090127/
  26. Québec, Act respecting the protection of personal information in the private sector, CQLR c P-39.1, ss. 8 and 17. Text current to 15 October 2026; retrieved 26 Oct 2026
    https://www.legisquebec.gouv.qc.ca/en/document/cs/P-39.1
  27. Twilio, HIPAA compliance page: BAA required for PHI, Security or Enterprise Edition needed to sign. Retrieved 26 Oct 2026
    https://www.twilio.com/en-us/hipaa
  28. Twilio, "HIPAA Eligible Services" (PDF): Programmable Voice items including Call Recordings and Storage*, Call Transcription*, Voice Insights. Last updated 30 June 2026
    https://www.twilio.com/content/dam/twilio-com/global/en/other/hipaa/pdf/HIPAA-Eligible-Services.pdf
  29. Twilio, "Architecting for HIPAA on Twilio" (PDF): signature verification and HTTP auth on recordings required for HIPAA workflows. Dated 11.09.22
    https://docs-resources.prod.twilio.com/documents/architecting-for-HIPAA.pdf
  30. Twilio Docs, "PCI workflows": PCI Mode, irreversibility, <Pay>, one-year default retention of PCI voice recordings. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/voice/pci-workflows
  31. Twilio Docs, Error 20429 "Too many requests": REST concurrency, safe to retry with exponential backoff, Twilio-Concurrent-Requests header. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/api/errors/20429
  32. Twilio Docs, Test credentials: supported resources, magic numbers, no TwiML executed and no status callbacks. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/iam/test-credentials
  33. Twilio Docs, Twilio CLI quickstart: Homebrew and npm installation, twilio login, profiles. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/twilio-cli/quickstart
  34. twilio-cli source, src/commands/phone-numbers/update.js: voice-url and voice-fallback flags; localhost URLs rejected. main branch; npm release 7.2.0; retrieved 26 Oct 2026
    https://github.com/twilio/twilio-cli/blob/main/src/commands/phone-numbers/update.js
  35. Twilio Docs, TwiML <Gather>: input speech, SpeechResult, speechTimeout, actionOnEmptyResult. Retrieved 26 Oct 2026
    https://www.twilio.com/docs/voice/twiml/gather
  36. Twilio Changelog, DTMF support for A-D and W: lowercase w is a 0.5-second pause in SendDigits and <Play digits>. 5 May 2025
    https://www.twilio.com/en-us/changelog/dtmf-support-ABCD-W
  37. Twilio, Programmable Voice pricing, United States (per-minute rates, numbers, recording, storage, Voice Insights, speech recognition, TTS). Page states "Pricing current as of August 2026"; retrieved 26 Oct 2026
    https://www.twilio.com/en-us/voice/pricing/us
  38. Twilio, Programmable Voice pricing, Canada. Page states "Pricing current as of August 2026"; retrieved 26 Oct 2026
    https://www.twilio.com/en-us/voice/pricing/ca