Skip to content

Twilio Phone Numbers: Provisioning, Configuration, and Management

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Twilio Phone Numbers: Provisioning, Configuration, and Management. We cover key concepts, practical examples, and best practices to help you master this topic.

Twilio phone numbers are virtual telephone numbers that can send and receive SMS and voice calls, with configurable Webhooks for inbound communication and support for geographic number pools.

What You'll Learn

How to search and purchase Twilio phone numbers, configure SMS and voice webhooks, release unused numbers, port existing numbers from other carriers, manage number pools, and set geographic permissions.

Why It Matters

Phone numbers are the foundation of Twilio communications. Choosing the right numbers, configuring them correctly, and managing costs directly impacts deliverability, user experience, and monthly spend.

Real-World Use

DodaTech needs numbers in the US, UK, and India for customer support. They search for numbers with specific area codes, configure SMS and voice webhooks per number, and set up a pool for automatic failover.

flowchart LR
    A["Search\nAvailable Numbers"] --> B["Filter by\nArea Code / Country"]
    B --> C["Purchase\nNumber"]
    C --> D["Configure\nSMS Webhook"]
    C --> E["Configure\nVoice Webhook"]
    D --> F["Inbound SMS\nReady"]
    E --> G["Inbound Voice\nReady"]
    F --> H["Production\nUse"]
    G --> H
    style A fill:#f22f46,color:#fff
    style B fill:#dbeafe,stroke:#2563eb
    style H fill:#bbf7d0,stroke:#16a34a

Searching and Purchasing Numbers

import os
from twilio.rest import Client

client = Client(
    os.environ["TWILIO_ACCOUNT_SID"],
    os.environ["TWILIO_AUTH_TOKEN"]
)

# Search local numbers
def search_numbers(country="US", area_code=None, limit=5):
    params = {"country": country, "limit": limit}
    if area_code:
        params["area_code"] = area_code
    available = client.available_phone_numbers(country).local.list(**params)
    print(f"Found {len(available)} numbers:")
    for num in available:
        print(f"  {num.phone_number} ({num.locality}, {num.region})")
    return available

numbers = search_numbers("US", area_code=415)
# Expected output:
# Found 3 numbers:
#   +14155551234 (San Francisco, CA)
#   +14155555678 (San Francisco, CA)
#   +14155559012 (San Francisco, CA)

# Purchase a number
def purchase_number(phone_number, friendly_name=None):
    params = {
        "phone_number": phone_number,
        "sms_url": "https://example.com/sms",
        "voice_url": "https://example.com/voice"
    }
    if friendly_name:
        params["friendly_name"] = friendly_name
    purchased = client.incoming_phone_numbers.create(**params)
    print(f"Purchased: {purchased.phone_number}")
    print(f"SID: {purchased.sid}")
    print(f"SMS URL: {purchased.sms_url}")
    print(f"Monthly cost: ${purchased.bundle_sid or 'Standard'}")
    return purchased

# number = purchase_number("+14155551234", "DodaTech Support US")
# Expected output:
# Purchased: +14155551234
# SID: PNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# SMS URL: https://example.com/sms
# Monthly cost: Standard

Configuring Number Capabilities

# Update number capabilities after purchase
def configure_number(number_sid, sms_url, voice_url, friendly_name=None):
    params = {
        "sms_url": sms_url,
        "sms_method": "POST",
        "voice_url": voice_url,
        "voice_method": "POST"
    }
    if friendly_name:
        params["friendly_name"] = friendly_name
    updated = client.incoming_phone_numbers(number_sid).update(**params)
    print(f"Updated: {updated.phone_number}")
    print(f"  SMS: {updated.sms_url}")
    print(f"  Voice: {updated.voice_url}")
    return updated

# configure_number("PNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
#                  "https://example.com/sms",
#                  "https://example.com/voice",
#                  "DodaTech Support Hotline")
# Expected output:
# Updated: +14155551234
#   SMS: https://example.com/sms
#   Voice: https://example.com/voice

# List all owned numbers
def list_owned_numbers():
    numbers = client.incoming_phone_numbers.list()
    print(f"Total numbers: {len(numbers)}")
    for num in numbers:
        print(f"  {num.phone_number} | {num.friendly_name}")
        print(f"    SMS: {num.sms_url or 'Not configured'}")
        print(f"    Voice: {num.voice_url or 'Not configured'}")
        print()

# list_owned_numbers()

Releasing Numbers

def release_number(number_sid):
    """Release a phone number (stops billing)."""
    deleted = client.incoming_phone_numbers(number_sid).delete()
    if deleted:
        print(f"Released number SID: {number_sid}")
        print("Monthly billing stopped.")
    else:
        print("Failed to release number.")
    return deleted

# release_number("PNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
# Expected output:
# Released number SID: PNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Monthly billing stopped.

# Find numbers not used in 30+ days
def find_unused_numbers(days_unused=30):
    from datetime import datetime, timedelta
    cutoff = datetime.utcnow() - timedelta(days=days_unused)
    numbers = client.incoming_phone_numbers.list()
    unused = []
    for num in numbers:
        # Check if number was ever used
        calls = client.calls.list(to=num.phone_number, limit=1)
        messages = client.messages.list(to=num.phone_number, limit=1)
        if not calls and not messages:
            unused.append(num)
            print(f"Unused: {num.phone_number}")
    return unused

Porting Numbers

# Port an existing number from another carrier
def initiate_port(phone_number, carrier_info):
    """
    carrier_info = {
        "carrier_name": "Verizon Wireless",
        "account_number": "VZW123456",
        "account_pin": "1234",
        "billing_telephone": "+14155551234",
        "address": {
            "street": "123 Main St",
            "city": "San Francisco",
            "state": "CA",
            "postal_code": "94105"
        }
    }
    """
    porting = client.porting.ports.create(
        phone_number=phone_number,
        **carrier_info
    )
    print(f"Port request: {porting.sid}")
    print(f"Status: {porting.status}")
    print("Porting takes 5-10 business days.")
    return porting

# Expected output:
# Port request: POxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Status: pending
# Porting takes 5-10 business days.

Common Mistakes

1. Buying Numbers Without Checking Capabilities

Some numbers support SMS only, voice only, or both. Check the capabilities field before purchasing. Use voice, sms, and mms booleans to confirm.

2. Forgetting to Release Unused Numbers

Each number costs $1-2/month. A dozen unused numbers cost $144-288/year. Regularly audit and release unused numbers. Set up automated cleanup scripts.

3. Not Configuring Webhooks at Purchase Time

Numbers purchased without webhooks will silently drop inbound messages and calls. Always set sms_url and voice_url during purchase or immediately after.

4. Porting Without Verifying Eligibility

Not all numbers can be ported. Verify portability using the Twilio Lookup API before initiating the port. Number pool, VoIP, and some toll-free numbers cannot be ported.

5. Using the Wrong Number for Geographic Routing

Sending SMS from a UK number to US customers may appear as international spam. Use numbers local to your recipients for higher deliverability and lower costs.

Practice Questions

  1. How do you search for available phone numbers in a specific area code?
  2. What happens if you configure a number without SMS and voice webhooks?
  3. How do you release a phone number to stop billing?
  4. What is number porting and how long does it take?

Answers:

  1. Call client.available_phone_numbers('US').local.list(area_code=415) to search. Filter by capabilities, SMS, voice, and MMS as needed.
  2. Inbound SMS and calls are silently dropped. The caller may hear silence or the message disappears. Always configure webhooks immediately after purchase.
  3. Call client.incoming_phone_numbers(sid).delete() to release. Billing stops immediately. The number returns to the available pool after a short period.
  4. Porting moves an existing number from another carrier to Twilio. It takes 5-10 business days. During porting, service continues on the old carrier until the port completes.

Challenge: Build a number management system: search and purchase 3 numbers in different area codes (415, 212, 312), configure each with unique SMS/voice webhooks, create a number audit that flags unused numbers, implement a port request for an existing number, and generate a monthly cost report.

FAQ

How much does a Twilio phone number cost?

Local numbers: $1.15/month. National numbers: $1.50/month. Toll-free: $2.00/month. SMS and voice usage is billed separately per message/minute.

Can I have multiple numbers for different purposes?

Yes, you can have unlimited numbers. Use different numbers for support, marketing, transactions, and notifications to track response rates per channel.

What is a Messaging Service vs a phone number?

A Messaging Service is a pool of numbers with intelligent routing, failover, and A2P 10DLC compliance. Use it instead of individual numbers for production SMS.

Can I use the same number for SMS and Voice?

Yes, numbers support both SMS and voice simultaneously. Configure separate webhook URLs for each. Twilio routes messages to the correct handler based on the channel.

How do I prevent buying numbers in expensive countries?

Use the Twilio Console to set geographic permissions. Whitelist specific countries where you want to buy numbers and block others.

Mini Project

Build a number lifecycle management system: search and purchase 2 numbers with different capabilities, configure SMS and voice webhooks, write an audit script that identifies unused numbers, simulate a number port request, implement a monthly cost tracking report, and set geographic purchase restrictions.

What's Next

Messaging Service — scale SMS sending with pools and failover.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro