#4569: Dialing Without a Switchboard: Twilio Number Normalization

Twilio expects E.164, but Israel dials 0-3-... Here's what breaks when you become your own carrier.

Featuring
Listen
0:00
0:00
Episode Details
Episode ID
MWP-4748
Published
Duration
26:19
Audio
Direct link
Pipeline
V5
TTS Engine
chatterbox-regular
Script Writing Agent
deepseek-v4-pro

AI-Generated Content: This podcast is created using AI personas. Please verify any important information independently.

When you dial a phone number, a small army of translation logic runs behind the scenes — logic your traditional carrier has always absorbed without you noticing. Move to Twilio, and that logic lands on your desk.

Twilio's core routing engine expects numbers in E.164 format: a plus sign, country code, and national significant number. The documentation carves out exactly one exception: unformatted US numbers, because the North American Numbering Plan is uniform enough to parse deterministically. Everywhere else, you're responsible for normalization.

That means a Tel Aviv number dialed as 0-3-1234567 — with the trunk prefix zero intact — won't route. Twilio either rejects it or, worse, tries to route it as a US call. And star codes like *3000, Israel's service code for municipal hotlines, aren't phone numbers at all. They have no E.164 representation. Twilio has no concept of them.

The fix is the Lookup API, which normalizes raw strings into E.164 — but it's a separate call you must integrate into your webhook flow yourself. Add service code mapping, area code inference, and emergency routing, and you've rebuilt the invisible switchboard from scratch. That's the real cost of becoming your own carrier.

Downloads

Episode Audio

Download the full episode as an MP3 file

Download MP3
Transcript (TXT)

Plain text transcript file

Transcript (PDF)

Formatted PDF with styling

#4569: Dialing Without a Switchboard: Twilio Number Normalization

Corn
Daniel's been deep in the telephony migration weeds, and this week he's asking about something that sounds simple but unravels into one of the messiest parts of running your own phone stack. Here's what he wrote.
Corn
"In recent episodes we've talked about telephony, and I mentioned I'm moving my business and personal phone lines over to Twilio. My goal isn't just to change providers, it's to gain much more control and flexibility than a traditional phone company offers. We've already explored voicemail, local phone numbers, and emergency calling. Today I'd like to focus on one very specific topic: how phone numbers are interpreted when you dial them."
Corn
"Here in Israel, as in many other countries, there are special service numbers such as star three thousand. These short codes are designed to be easy to remember and are generally only meaningful within that country's telephone network. If they weren't, there would obviously be conflicts between different countries' services. The same is true for ordinary phone numbers. Most people dial them in whatever local format they're used to, even though the network ultimately has to translate those numbers into the international E dot one six four format before the call can be routed correctly."
Corn
"So I'm curious about how Twilio handles all of this. If you're using Twilio as the core of your phone service rather than a traditional carrier, does Twilio automatically understand local dialing conventions and country-specific short codes, or do you need to explicitly configure translation rules? In other words, how much of the quote it just works unquote behavior that people take for granted with a normal phone provider is built into Twilio, and how much becomes your responsibility once you're running your own telephony stack?"
Corn
There's a lot here. The short version of the answer is: almost none of it is built in. But the long version is where this gets interesting.
Herman
The long version is where you realize you've been outsourcing an entire category of thinking to your phone company your whole life. And the moment you become your own carrier, that thinking lands on your desk.
Corn
Daniel's asking, essentially, who's responsible for making sense of what you dial. And the answer shifts completely when you move from renting a phone company to owning the switch.
Herman
Right. Let's start with what Twilio actually does with a phone number you hand it. Because the answer is genuinely surprising if you haven't read the docs.
Herman
Twilio's core routing engine, the thing that handles calls and SMS, expects phone numbers in E dot one six four format. That's the international numbering plan standard. It's structured as a plus sign, then a country code, then the national significant number. Maximum fifteen digits total. So plus nine seven two, then the rest.
Corn
Plus nine seven two five four something something something.
Herman
That's the universal routing format. Every carrier on earth understands it. When you feed Twilio an E dot one six four number, it routes it correctly, end of story.
Herman
Now, here's where the surprise lives. The documentation for the Number noun in TwiML, which is Twilio's markup language for call flow, says this. I'm quoting directly: "Phone numbers should be formatted in E dot one six four format with a plus and country code." Then there's a period. And then: "Twilio will also accept unformatted US numbers, for example, four one five five five five one two one two or four one five dash five five five dash one two one two."
Corn
Wait. So they carved out an entire exception just for the United States?
Herman
They did. And it's not hidden in some footnote. It's right there in the primary documentation for the most basic call-routing element in the entire platform.
Corn
That's... almost aggressively American.
Herman
It's not even aggressive. It's just the gravitational pull of where Twilio started. They're a San Francisco company. Their earliest customers were American. The North American Numbering Plan has a uniform ten-digit structure. Every US number is exactly three digits of area code plus seven digits of subscriber number. Normalizing that is trivial. Strip the parentheses, strip the dashes, count to ten, slap a plus one on the front, done.
Corn
So the US gets a free pass because the US numbering system is boring.
Herman
Boring in a way that makes parsing deterministic. But the moment you cross a border, that determinism evaporates. Take Israel. A Tel Aviv number in local format looks like zero three dash one two three four five six seven. That's ten digits including the leading zero. If you pass that string to Twilio's Number noun without any preprocessing, what does Twilio see?
Corn
A ten-digit string that doesn't start with a plus.
Herman
And doesn't match any US area code pattern, probably. So it either rejects it outright or, if those ten digits happen to align with a valid US number format, it tries to route it as a US call.
Corn
Which would mean someone in Tel Aviv dials their neighbor and gets connected to... what, a dentist in Cleveland?
Herman
Worst case, yes. Best case, the call fails with an invalid number error. Neither outcome is what you'd call a working phone system.
Corn
And the leading zero is the real trap here. Because in Israel, you dial zero three for Tel Aviv, zero two for Jerusalem. That zero is the trunk prefix. It tells the local exchange "this is a domestic long-distance call within the country." But in E dot one six four, that zero gets stripped. The country code replaces it. So zero three one two three four five six seven becomes plus nine seven two three one two three four five six seven.
Herman
And Twilio does none of that stripping for you. If you feed it the zero-three version, it doesn't know what to do with it. This is the first big realization: outside the United States, Twilio assumes you've already done the normalization work before the number hits its API.
Corn
So the "it just works" layer that Daniel's asking about? For international numbers, it's not there.
Herman
It's not there. And this is by design, not by oversight. Twilio's philosophy is that it gives you primitives, not a finished phone company. The tradeoff is you get complete control over call routing, but you also get complete responsibility for making sure the inputs to that routing are valid.
Corn
Let's talk about the star codes. This is where it gets even weirder.
Herman
The star three thousand problem. So in Israel, if you dial star three thousand from a regular cell phone or landline, you reach... what exactly?
Corn
It's a general information and service line. Think of it like a municipal hotline or a government services portal. You dial those four characters and it connects you.
Herman
And here's the thing: star three thousand is not a phone number. Not in any sense that the international numbering plan recognizes. It has no E dot one six four representation. You can't write it as plus something something something. It's a service code. It exists only within the context of the Israeli telephone network.
Herman
When you dial it on a traditional carrier, your phone doesn't even send it as a number. It sends a signal that says "the user wants service code three thousand." The local exchange recognizes that code, looks it up in a table, and routes the call to whatever endpoint the carrier has mapped it to.
Corn
And Twilio has no concept of service codes.
Herman
None. Twilio's API understands phone numbers. That's it. If you pass star three thousand to the Number noun, Twilio looks at it and sees... a string that starts with an asterisk. It's not a valid E dot one six four number. It's not a valid US number. It's not anything Twilio knows how to route. The call fails.
Corn
So Daniel's question about whether Twilio "automatically understands" these short codes has a very clean answer: no, it does not understand them at all. They're not even in the same category of thing.
Herman
And this isn't a flaw in Twilio. It's a category difference. Traditional carriers operate a closed network. They control the entire chain from handset to exchange to destination. They can define whatever service codes they want because they own the namespace. Twilio operates at the API layer. It routes calls to and from the public telephone network using standard numbering. Service codes don't cross that boundary.
Corn
Unless you build the bridge yourself.
Herman
Which is exactly what you have to do. And we'll get to that.
Corn
First, let me make sure I understand the full scope of what a traditional carrier absorbs. We've got local number formatting, leading zero stripping, area code recognition, service code routing. What else?
Herman
Intra-country dialing variations. In Israel, if you're calling a number within the same area code, you can often drop the area code entirely and just dial the seven-digit subscriber number. The local exchange fills in the rest. Twilio has no idea what area code you're in, so it can't fill in anything.
Herman
There's also the emergency services routing we covered in a previous episode. Dialing one zero zero or one zero one from any phone in Israel connects you to police or ambulance, and the network knows your location. Twilio doesn't do that natively either.
Corn
So the list of things you're taking on when you become your own carrier is: number formatting, area code inference, trunk prefix handling, service code mapping, emergency routing. And that's just the dialing layer.
Herman
It's the invisible switchboard. The thing that's been quietly doing work for you since the day you got your first phone. And most people never think about it because it's invisible. You pick up the phone, you dial, it works. The fact that a small army of translation logic ran in the background is not something anyone appreciates until they try to replicate it.
Corn
Until they're Daniel, staring at a Twilio console, wondering why star three thousand doesn't connect.
Herman
So now that we know the gap, the real question is what you do about it. And this is where the Lookup API and some of your own code come in.
Corn
Let's talk about the Lookup API. What does it actually give you?
Herman
The Twilio Lookup API version two is a validation and normalization service. You send it a raw string, something a user typed into a dialer, and it returns structured data about that number. The key field for our purposes is the national format and the E dot one six four format. So if you send it zero three one two three four five six seven, it'll return plus nine seven two three one two three four five six seven.
Corn
It does the leading-zero stripping and country code prepending automatically?
Herman
For standard phone numbers, yes. It knows that Israel's country code is nine seven two. It knows that the zero-three prefix maps to area code three. It does the translation. But here's the catch: it's a separate API call.
Corn
Meaning it doesn't run automatically when a call comes in.
Herman
It does not. You have to explicitly integrate it into your call flow. When a call arrives at your Twilio number, the inbound webhook fires and sends you the To and From parameters. Those parameters contain whatever the caller dialed and whatever number they're calling from, in whatever format the carrier passed them. If the carrier passed them in local format, that's what you get. Twilio doesn't normalize them for you before hitting your webhook.
Corn
So your application receives the raw input, and it's your job to say "okay, before I route this, let me clean it up."
Herman
You'd write a function that intercepts every inbound call, takes the To and From numbers, runs them through the Lookup API, and replaces them with the normalized E dot one six four versions before proceeding with your routing logic.
Corn
And that works for standard phone numbers. But we already established that star three thousand is not a standard phone number.
Herman
Right. The Lookup API won't help you with star three thousand. If you send it a string that starts with an asterisk, it's going to return an error or an empty result. Because star three thousand is not a phone number. It's not in any numbering plan database. The Lookup API validates phone numbers. Service codes are outside its scope entirely.
Corn
So for service codes, you need something else entirely.
Herman
You need a mapping table. A simple key-value store in your application that says: if the user dialed star three thousand, route the call to this TwiML bin, or this function, or this external number. You're essentially recreating the lookup table that exists inside the traditional carrier's exchange.
Corn
And you have to populate that table yourself.
Herman
You do. Twilio doesn't publish a global directory of service codes. How would it? Every country defines its own. Some carriers within a country define additional ones. There's no central authority for "things that start with asterisks." So you research the codes that matter for your users, you map them to endpoints, and you maintain that mapping as services change.
Corn
This is the philosophical core of the whole migration, isn't it? When you run your own telephony stack, you're not just a customer anymore. You're the switch.
Herman
The dialing plan, which is the set of rules that determines how numbers are interpreted and routed, is now your code. It's not a configuration option hidden in some carrier's backend that you never see. It's a function you wrote, in a file you can open, that does exactly what you tell it to do.
Corn
And that's both the burden and the opportunity. The burden is obvious: you have to write it. The opportunity is that you can implement behavior no traditional carrier would ever offer.
Herman
Give me an example.
Corn
Say you want star three thousand to route to a different destination depending on the time of day. Or depending on which of your Twilio numbers the caller dialed. Or you want it to play a custom message first, then connect. A traditional carrier gives you whatever routing they've pre-configured. You get zero control. With your own stack, star three thousand can do literally anything you can express in code.
Herman
And that's the reframe. It's not that you're losing the "it just works" behavior. It's that you're dismantling it and rebuilding it with knobs you can actually turn.
Corn
Let's get concrete about the architecture. If Daniel's building this for Israel, what does the inbound call flow actually look like?
Herman
Call arrives at his Twilio number. The webhook fires. His application receives the HTTP request. First thing it does is check the To parameter. Does it start with an asterisk or a hash?
Corn
Hash being the pound sign, for American listeners.
Herman
Right. If it starts with an asterisk or hash, it's a service code. Skip the Lookup API entirely, it won't help. Instead, look it up in the service code mapping table. If there's a match, route to the configured endpoint. If there's no match, play an error message or connect to a fallback.
Corn
And if it doesn't start with a special character?
Herman
Then it's probably a phone number. Pass it through the Lookup API. Get back the normalized E dot one six four version. Use that for routing. If the Lookup API returns an error, the number might be malformed. Play a message saying the number couldn't be recognized, or try a best-guess normalization using a library.
Corn
Which brings us to libraries. You mentioned something earlier about Google's phone number library.
Herman
libphonenumber. This is a library Google maintains that handles phone number parsing, validation, and formatting for basically every country on earth. It knows about leading zeros and trunk prefixes. It knows that in Israel you dial zero three for Tel Aviv but plus nine seven two three for international. It knows which area codes are valid in which countries. It's the closest thing to a universal dialing plan in a box.
Corn
And it's free and open source.
Herman
It is. There are ports in Java, C plus plus, JavaScript, Python. You can run it in your own application without making an external API call. It's faster than the Lookup API for simple normalization, and it works offline.
Corn
So why use the Lookup API at all?
Herman
Because libphonenumber tells you whether a number is validly formatted. It doesn't tell you whether the number is currently assigned to a real phone. The Lookup API can return carrier information, caller name in some cases, and most importantly, it can tell you the current line type. Is this a mobile number? A landline? A VoIP number? That's data libphonenumber doesn't have.
Corn
So the two tools solve different problems. libphonenumber handles formatting. The Lookup API handles validation against live data.
Herman
And for most normalization use cases, libphonenumber is enough. You're not trying to verify that the number exists. You're just trying to turn zero three one two three four five six seven into plus nine seven two three one two three four five six seven. libphonenumber does that in a single function call, no network request required.
Corn
Let's talk about what happens on the outbound side. Daniel's building a system where he can make calls through Twilio. What does he need to normalize on the way out?
Herman
Same thing, but the stakes are different. On an inbound call, if normalization fails, the call doesn't connect and the caller hears an error. Annoying, but survivable. On an outbound call, if you pass a malformed number to Twilio's API, the call attempt fails and you get charged for it anyway if you're using certain pricing models.
Corn
You want to catch formatting problems before they hit the API.
Herman
Your dialer interface should normalize on input. As the user types, or when they hit dial, run the number through libphonenumber. Show them the normalized version. If it can't be parsed, show an error before the call is even attempted. Don't waste an API call on a number that's obviously broken.
Corn
For the service codes on outbound?
Herman
If you're building a dialer that lets users type star codes, you need the same mapping table. But honestly, most outbound dialing from a custom stack is going to be standard phone numbers. The service code problem is primarily an inbound one.
Corn
Because the people calling you are the ones who might dial star three thousand.
Herman
Right. Your users, your callers, they're operating with the mental model of a traditional phone. They dial what they've always dialed. Your system has to meet them where they are.
Corn
This is where the "it just works" expectation collides with reality. Daniel's callers aren't going to change their behavior just because he moved to Twilio. They're going to dial zero three whatever, or star three thousand, and expect it to connect.
Herman
If it doesn't, they're not going to think "ah, Daniel must have migrated to a programmable telephony platform without implementing a full dialing plan normalization layer." They're going to think his phone system is broken.
Corn
Which it is, from their perspective.
Herman
Fair.
Corn
The normalization layer isn't optional. It's not a nice-to-have. It's the thing that makes your custom phone stack behave like a phone.
Herman
This is where I think a lot of people who start down the Twilio migration path get surprised. They think they're just changing providers. They don't realize they're signing up to reimplement a chunk of the public switched telephone network in their application code.
Corn
Is that hyperbole?
Herman
A little. You're not reimplementing the physical switching infrastructure. You're not running fiber. But you are reimplementing the logic layer. The part of the phone company that knows what to do when someone dials a number that doesn't look like plus something.
Corn
Let's distill this into some rules. For Daniel, and for anyone else thinking about this migration.
Herman
Rule one: assume nothing is normalized outside the United States. If you're building for any country other than the US, plan for a normalization step on every inbound and outbound call. No exceptions.
Corn
If you're building for the US, still plan for it, because the moment you have one international caller or one user dialing an international number, the US exception stops helping you.
Herman
Good point. Rule two: use the Lookup API for validation against live data, but don't rely on it for short codes or service numbers. Those need a custom mapping table in your application logic. The Lookup API validates phone numbers. Service codes are not phone numbers.
Corn
Rule three: consider using libphonenumber as the foundation of your normalization layer. It handles country-specific dialing rules, leading-zero stripping, and formatting edge cases that you would otherwise have to research and implement yourself. And it runs locally, no API call needed.
Herman
Rule four, which is more of a mindset shift: "it just works" is a feature you have to build, not a default you inherit. But that's the whole point of moving to Twilio. You're trading convenience for control, and the dialing plan is one of the first places you feel that trade.
Corn
I want to push on that trade a little. Because "trading convenience for control" makes it sound like you're losing something. But the convenience you're losing is the convenience of not having to think about how your phone system works. And for a lot of people, that convenience came with a lot of constraints they didn't choose.
Herman
That's fair. The traditional carrier's dialing plan is convenient, but it's also opaque and inflexible. You can't modify it. You can't add your own service codes. You can't route calls differently based on context. You get what the carrier gives you.
Corn
Whereas with your own stack, star three thousand can do anything. It can trigger a workflow. It can query a database. It can forward to a different number depending on who's calling. The carrier's version of star three thousand is a single destination hard-coded in their exchange. Your version is software.
Herman
That's the opportunity. The dialing plan becomes a programmable surface. It's not just a lookup table anymore. It's logic.
Corn
Before we wrap, there's a bigger question lurking here. As Twilio and similar platforms expand globally, do we see more country-specific normalization built in, or is the "bring your own dialing plan" model here to stay?
Herman
My guess is it's here to stay, at least at the API level. Twilio's value proposition is that it gives you primitives, not a finished product. Building country-specific normalization into the core routing layer would add complexity that most of their global customers don't need, and it would create expectations they can't consistently meet across two hundred countries.
Herman
What I think we might see is better tooling around the normalization problem. More SDKs that bundle libphonenumber. Pre-built functions in the Twilio console that handle common normalization patterns. Maybe a managed service that does for dialing plans what Twilio did for voice and messaging.
Corn
A dialing-plan-as-a-service.
Herman
Someone's going to build that. The demand is clearly there.
Corn
The other implication worth carrying forward is what this means for regulatory compliance and number portability. If the dialing plan is becoming a software artifact, who's responsible when it breaks? The developer who wrote the normalization function? The platform that routed the call?
Herman
Right now, the answer is the developer. Twilio's terms are pretty clear that you're responsible for the legality and correctness of your application. If your normalization layer routes emergency calls incorrectly, that's on you.
Corn
Which is why the emergency services question we covered previously is so tightly coupled to this one. The dialing plan and emergency routing are the same problem from different angles.
Herman
They're both about taking responsibility for logic that used to be someone else's job.
Corn
The single biggest misconception people have about this topic is that Twilio handles phone numbers like a carrier does. The reality is it only auto-normalizes US numbers. Everything else has to be pre-formatted to E dot one six four or normalized by the developer. And short codes like star three thousand aren't phone numbers at all, they're service codes that require custom routing logic.
Herman
The Lookup API, for all its usefulness, is not a magic bullet. It validates standard phone numbers. It won't help with service codes, and it's an explicit API call you have to build into your flow. It doesn't run automatically.
Corn
This whole migration Daniel's doing is a fascinating case study in what you actually inherit when you become your own carrier. It's not just about cheaper calls or more flexible routing. It's about discovering all the invisible machinery that was quietly running inside your old phone company and realizing that machinery is now yours to build, maintain, and improve.
Herman
If you do it right, you end up with something better than what you left behind. Not because it's easier, but because it's yours.
Corn
Our thanks as always to producer Hilbert Flumingtop for making this show happen.
Herman
This has been My Weird Prompts. If you've got a question like Daniel's, something that seems simple on the surface but unravels into a whole world of hidden complexity, send it our way. Email the show at show at my weird prompts dot com.
Corn
We'll be back soon with more from the telephony migration trenches.

This episode was generated with AI assistance. Hosts Herman and Corn are AI personalities.