Every SMS, call, OTP and roaming record starts with a phone number. Numbers look simple, but formats differ by country, change over time, and are easy to store or validate wrongly. This module explains the standard (E.164), the differences between countries, how numbers appear in telecom protocols (SMPP, MAP), and how to validate them correctly.
How the examples were checked. Country rules and examples below were read from the open-source libphonenumber metadata (Google, Apache licence; 246 regions, 207 distinct country codes in the version consulted in November 2026) and from the SMPP value tables in Wireshark. All sample numbers are the example numbers in that metadata, not real subscribers. Numbering plans change; regulators publish the official current plans.
1. What E.164 is
ITU-T E.164 defines the international public telecommunication numbering plan. A number in E.164 form is:
+ CC NSN
│ │ └─ National Significant Number (area/network code + subscriber number)
│ └───── Country Code: 1 to 3 digits (e.g. 1, 44, 385)
└───────── "+" means "replace your local international dialling prefix"
Total digits (CC + NSN): at most 15
| Rule | Detail |
|---|---|
| Maximum length | 15 digits after the "+" (country code included) |
| Country code | 1–3 digits, prefix-free (no code is the start of another) |
| Only digits | No spaces, hyphens or brackets in the stored form |
| "+" | A display sign for the international prefix; it is not dialled |
| Trunk prefix | The national "0" (or "1" in the US) is not part of E.164 |
| International call prefix | What you dial to leave your country (00, 011, 010, 0011…), not part of the number |
(The 15-digit limit and 1–3-digit country codes are widely documented in secondary sources; the recommendation text itself was not read in full.)
Why "+385" is better than "00385"
The "+" is location-independent: a mobile phone converts it into whatever international prefix the local network needs (00 in most of Europe, 011 in the US/Canada, 010 in Japan). A stored "00385…" only works for people who dial 00.
2. Four ways to write the same number (Croatia, example number)
| Form | Written as | Use |
|---|---|---|
| E.164 (machine form) | +385921234567 |
Databases, APIs, SMS, billing, signalling |
| International display | +385 92 123 4567 |
Printed and shown to users (ITU E.123 style) |
| National display | 092 123 4567 |
Dialling inside the country (trunk prefix 0 appears) |
| tel URI | tel:+385-92-123-4567 (RFC 3966) |
Links, SIP, web |
Rule of thumb: store E.164, show national or international, never mix them in the database.
3. Anatomy of a number (Croatia, from libphonenumber metadata)
| Type | Pattern in plain words | Length | Example (E.164) |
|---|---|---|---|
| Mobile | starts with 9 and then specific ranges (e.g. 91, 92, 95, 97, 98, 99) | 8–9 digits after +385 | +385921234567 |
| Fixed line | starts with 1 (Zagreb) or one of the regional codes 20–23, 31–35, 40, 42–49, 51–53 | 8–9 digits | +38512345678 |
| Toll-free | starts with 80 | 7–9 digits | +385800123456 |
| Premium rate | starts with 60, 61, 64, 65, 69 | 7–8 digits | +3856001234 |
(That the code 1 belongs to Zagreb is general knowledge; the metadata only lists the allowed ranges.)
Important: a mobile prefix no longer tells you the operator — number portability lets a customer keep the number when changing operator (Modules 14 and 29). To know the current network you must ask a database or do an HLR lookup.
4. A country table (from the metadata)
| Region | Country code | Intl. dialling prefix | Trunk prefix | Example mobile (E.164) | Mobile length | Fixed length |
|---|---|---|---|---|---|---|
| Croatia | +385 | 00 | 0 | +385921234567 | 8–9 | 8–9 |
| Slovenia | +386 | 00 (several) | 0 | +38631234567 | 8 | 8 |
| Bosnia and Herzegovina | +387 | 00 | 0 | +38761123456 | 8–9 | 8 |
| Serbia | +381 | 00 | 0 | +381601234567 | 8–10 | 7–12 |
| Montenegro | +382 | 00 | 0 | +38260123456 | 8 | 8 |
| Hungary | +36 | 00 | 06 | +36201234567 | 9 | 8 |
| Austria | +43 | 00 | 0 | +43664123456 | 7–13 | 4–13 |
| Italy | +39 | 00 | none (the 0 is kept) | +393123456789 | 9–10 | 6–12 |
| Germany | +49 | 00 | 0 | +4915123456789 | 10–11 | 5–15 |
| Switzerland | +41 | 00 | 0 | +41781234567 | 9 | 9 |
| United Kingdom | +44 | 00 | 0 | +447400123456 | 10 | 9–10 |
| France | +33 | 00 | 0 | +33612345678 | 9 | 9 |
| Spain | +34 | 00 | none | +34612345678 | 9 | 9 |
| Poland | +48 | 00 | none | +48512345678 | 9 | 7, 9 |
| United States | +1 | 011 | 1 | +12015550123 | 10 | 10 |
| China | +86 | 00 (several) | 0 | +8613123456789 | 11 | 7–11 |
| India | +91 | 00 | 0 | +918123456789 | 10 | 10 |
| Brazil | +55 | 00 + carrier code | 0 | +5511961234567 | 10–11 | 10 |
| Japan | +81 | 010 | 0 | +819012345678 | 10 | 9 |
| Australia | +61 | several | 0 | +61412345678 | 9 | 9 |
| Turkey | +90 | 00 | 0 | +905012345678 | 10 | 10 |
| Nigeria | +234 | 009 | 0 | +2348021234567 | 10 | 10 |
| South Africa | +27 | 00 | 0 | +27711234567 | 5–9 | 9 |
What this table teaches
- Trunk prefix differs: 0 in most countries, 06 in Hungary, 1 in the US/Canada, none in Italy, Spain and Poland — in Italy the leading 0 of a fixed number stays even in international form (
+39 02 1234 5678). - Lengths vary: Austria's mobile numbers can be 7–13 digits, Germany's fixed numbers 5–15 — you cannot validate with "10 digits".
- International prefix differs: 00, 011 (US), 010 (Japan), 009 (Nigeria) and country-specific carrier codes (Brazil) — hence "+" in storage.
- US: fixed and mobile numbers cannot be told apart from the number alone (same pattern).
5. Country codes that are shared
In the metadata, 12 country codes are shared by several regions. Examples:
| Code | Regions |
|---|---|
| +1 (NANP, North American Numbering Plan) | US, Canada and 23 other regions (Caribbean and Pacific territories) — told apart by the area code (first 3 digits) |
| +7 | Russia, Kazakhstan |
| +39 | Italy, Vatican City |
| +44 | UK, Guernsey, Isle of Man, Jersey |
| +61 | Australia, Christmas Island, Cocos (Keeling) Islands |
| +358 | Finland, Åland |
| +590 | Guadeloupe, Saint-Barthélemy, Saint-Martin |
| +599 | Bonaire/Sint Eustatius/Saba, Curaçao |
| +47, +212, +262, +290 | Norway/Svalbard, Morocco/Western Sahara, Réunion/Mayotte, Saint Helena/Tristan da Cunha |
Consequence: a country code is not enough to identify the country; software needs the region and the leading digits, and billing/regulation (e.g. +1 premium numbers, SMS pricing) depends on it.
6. Number types and special numbers
| Type | Meaning | Remarks |
|---|---|---|
| Fixed line | Geographic number tied to an area code | In several countries cannot be told from mobile |
| Mobile | Mobile number | Prefix does not equal operator (portability) |
| Toll-free | Caller does not pay (Croatia 80…, US 800/833/844…) | Usually domestic only |
| Premium rate | Caller pays extra (Croatia 6x) | Fraud risk (IRSF, Wangiri, Modules 26–27) |
| Shared cost | Caller and callee share cost (Germany 180) | Country-specific |
| VoIP / personal numbers | Non-geographic IP numbers (UK 56…) | Often used by businesses and fraudsters |
| Short codes | 4–6 digits for services (SMS A2P) | Not E.164; operator-specific |
| Emergency (112) | Not E.164; works without roaming agreement | Module 21 |
| Harmonised EU numbers (116…) | Social services (Module 22) | Same across EU |
| Alphanumeric sender ID | Brand name instead of a number in SMS | Up to 11 characters in SMS (3GPP TS 23.040); not a real number |
| IMSI, ICCID, IMEI | Different identity types, not phone numbers | Module 14 |
7. Numbers inside telecom protocols
7.1 MSISDN versus the other numbers
- MSISDN = the E.164 phone number of a subscriber.
- IMSI (E.212) identifies the SIM; Global Titles in SS7 are E.164-based (and E.214 for mobile GTs derived from IMSI); Point Codes are separate (Modules 4 and 14).
7.2 TON and NPI — how a number is labelled
Many protocols carry a number together with two labels: Type of Number (TON) and Numbering Plan Indicator (NPI). Values below were checked in the SMPP tables used by Wireshark:
| TON | Meaning | NPI | Meaning | |
|---|---|---|---|---|
| 0 | Unknown | 0 | Unknown | |
| 1 | International (E.164 with country code) | 1 | ISDN / E.163/E.164 telephony | |
| 2 | National | 3 | Data (X.121) | |
| 3 | Network specific | 4 | Telex (F.69) | |
| 4 | Subscriber number | 6 | Land mobile (E.212) | |
| 5 | Alphanumeric (sender names) | 8 | National | |
| 6 | Abbreviated (short code) | 9 | Private | |
| 10 / 14 / 18 | ERMES / Internet (IP) / WAP client ID |
Practical rules for SMPP senders: a normal international number uses TON 1, NPI 1; an alphanumeric sender uses TON 5; short codes use TON 3/6 depending on the operator; wrong TON/NPI is a classic cause of rejected messages or messages delivered with the wrong sender (Module 23: SMPP trace).
7.3 Where formatting errors hurt in the network
| Symptom | Cause |
|---|---|
| SMS rejected "invalid destination" (SMPP 0x0B) | Missing country code, wrong TON/NPI, trunk prefix included (+385 (0)92…) |
| OTP never arrives | Number stored without country code or with a leading 0; wrong region |
| HLR lookup says "unknown" | Malformed number or non-existent range |
| Wrong routing in number portability | Lookup done on the wrong form (national vs international) |
| Billing errors / duplicates | Same subscriber stored as 0921234567 and +385921234567 |
| Spoofing and display issues | Sender shown in non-E.164 form; alphanumeric sender hijacking |
8. Validation — the six levels
| Level | Question | How |
|---|---|---|
| 1. Syntax | Only digits (and a leading +)? | Strip spaces/hyphens/brackets safely |
| 2. Possible | Right length for the region? | isPossibleNumber (length only; fast, loose) |
| 3. Valid | Does the number fit an allocated pattern for that region? | isValidNumber (length + prefix patterns from metadata) |
| 4. Type | Mobile, fixed, toll-free, premium… | getNumberType — critical for SMS (only mobiles can receive SMS) |
| 5. Reachable | Does the number exist now on a network? | HLR lookup (SRI-SM, Module 14), carrier lookup APIs, number-verification APIs |
| 6. Owned | Is it controlled by this user? | OTP/verification (and SIM-swap checks, Module 29) |
Common mistake: treating level 3 ("valid format") as proof the number is real, active or the user's. It only says the digits fit a pattern.
Why a regex is not enough
- Patterns are long and change (new ranges are allocated; e.g. the US pattern lists dozens of area-code ranges; the German pattern is hundreds of characters).
- Variable lengths (Austria 7–13 mobile digits; Germany fixed 5–15).
- Shared codes and territories (+1, +44).
- Region-dependent parsing:
0921234567is valid in Croatia but means something else elsewhere — parsing needs a default region. - Use a maintained library (libphonenumber and its ports for Java, JavaScript, Python, PHP and others), updated regularly, and still keep levels 5–6 for real verification.
9. How to store and handle numbers (checklist)
- Store E.164 as text (
"+385921234567"), never as an integer (you lose leading zeros and the "+"). - Keep the original input and the region you assumed when parsing.
- Normalise on entry, display in national/international format on output.
- Do not drop or add the trunk "0" by hand; let the library do it per country.
- Handle extensions (
ext./x) separately — they are not part of E.164. - Treat numbers as personal data (GDPR, Module 22); limit logging and retention.
- Plan for recycling: an old number can be reassigned to someone else — check "last verified" dates.
- Re-check periodically: numbering plans change.
- Test with real range examples for the countries you serve, including shared codes and short codes.
- Use alphanumeric senders carefully: they are not numbers, may need registration, and cannot receive replies.
10. Further reading (and one source to treat with care)
- ITU-T E.164 (numbering plan), E.123 (written notation), E.212 (IMSI), E.214 (mobile GTs); RFC 3966 (tel URI).
- libphonenumber (metadata and README): the practical reference for patterns and examples.
- National regulators' numbering plans (e.g. HAKOM's Plan adresiranja, Modules 14 and 22) for the official current ranges.
- Sent.dm "Phone number standards" — an index of 225 country guides by a commercial messaging platform; useful as a starting list, but it cites no sources and contains inconsistencies (for example Bosnia and Herzegovina appears twice with different patterns), so verify any rule against the regulator or libphonenumber.
Remember this: a phone number is "+" + country code + national significant number, at most 15 digits; store that, show it nicely, and remember that format-valid is not the same as existing or yours. Prefixes tell you the type and region, not the current operator.