Guides · Updated 2026-10-20

How to validate phone numbers (and spot VoIP numbers)

Parse what people type, store it in E.164, reject impossible numbers, and flag VoIP, premium-rate and toll-free lines, with code for every stack.

The short answer: parse the number with the person's country as a hint, reject it if it isn't a valid range, store it in E.164 (+14155552671), and look at the line type: mobile is what you want for SMS, VoIP and premium-rate numbers deserve a second look.

Why a regex doesn't work

Phone numbers vary by country in length, prefixes and allowed ranges. 07400 123456 is a valid UK mobile; 0740 012 3456 typed by a German user is something else entirely. A regex can only check digits. You need numbering-plan data for every country, which is what Google's libphonenumber provides and what Veqen uses.

The four steps

  1. Parse with a country hint. People type numbers in their national format. Pass their country (from a dropdown or their IP) so 07400 123456 can be read.
  2. Validate. valid means the number is in a range a carrier can assign. possible only means it has the right length.
  3. Store E.164. One canonical format, accepted by every SMS provider, easy to de-duplicate.
  4. Check the line type. Mobile for SMS codes. VoIP numbers are cheap to create in bulk. Toll-free and premium-rate numbers are never personal phones.

Code

curl https://veqen-app.com/v1/phone/check \
  -H "Authorization: Bearer $VEQEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone": "07400 123456", "country": "GB"}'
def normalize_phone(raw: str, country: str | None):
    r = requests.post(
        "https://veqen-app.com/v1/phone/check",
        headers={"Authorization": f"Bearer {os.environ['VEQEN_API_KEY']}"},
        json={"phone": raw, "country": country},
        timeout=5,
    ).json()
    if not r["valid"]:
        raise ValueError("That phone number doesn't look right. Check the country and the digits.")
    if r["line_type"] in ("premium_rate", "toll_free", "pager"):
        raise ValueError("Please enter a personal mobile number.")
    return r["e164"], r["line_type"]
const r = await fetch("https://veqen-app.com/v1/phone/check", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.VEQEN_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ phone: input, country: "GB" }),
}).then((res) => res.json());
if (!r.valid) throw new Error("Please check the number.");
await db.user.update({ where: { id }, data: { phone: r.e164, phoneType: r.line_type } });

Country formats

Not sure what a number should look like? The phone formats directory shows calling codes, lengths and real examples for 245 countries, like the United Kingdom or Nigeria.

Frequently asked questions

Can you tell me if the number is active?

No. Validation tells you the number is a real, assignable range and what kind of line it is. Only sending an SMS or calling proves someone answers it.

Why does a US number say "fixed_line_or_mobile"?

US and Canadian numbering doesn't separate mobile and landline ranges, so the type can't be known from the number alone.