Back to Blog
Tutorial

How to Block Fake Phone Numbers in Python: Complete Guide

Learn how to validate phone numbers and prevent fake signups in your Python applications using real-time phone validation APIs.

Payloadic Team8 min read

Fake phone numbers are a major problem for web applications. They lead to failed user verifications, poor data quality, and wasted resources on SMS messages that never get delivered. In this comprehensive guide, you'll learn how to validate phone numbers in Python and block fake signups before they pollute your database.

Why Phone Validation Matters

Before we dive into the code, let's understand why phone validation is critical:

  • Reduce SMS costs: Sending verification codes to invalid numbers wastes money
  • Improve deliverability: Invalid phone numbers bounce, hurting your sender reputation
  • Block fraud: Fake numbers are often used for spam accounts and malicious activity
  • Better user experience: Catch typos during signup rather than after submission
  • Clean data: Keep your database filled with real, reachable contacts

According to industry research, up to 15% of phone numbers in user databases are invalid or undeliverable. That's a significant waste of resources.

What Makes a Phone Number "Fake"?

A phone number can be invalid for several reasons:

  1. Wrong format: Missing digits, incorrect country code, or invalid area code
  2. Disconnected: The number was valid once but is no longer in service
  3. VoIP/temporary: Numbers from services like Google Voice or burner apps
  4. Landline: Can't receive SMS messages (if you're sending verification codes)
  5. Geographic mismatch: Country code doesn't match the claimed location

A robust validation solution checks all these factors.

Method 1: Basic Format Validation with Regex

The simplest approach is to validate the phone number format using regular expressions:

import re

def is_valid_us_phone(phone: str) -> bool:
    """
    Validate US phone number format.
    Accepts: (123) 456-7890, 123-456-7890, 1234567890
    """
    pattern = r'^(\+1)?[\s\-\.]?\(?([0-9]{3})\)?[\s\-\.]?([0-9]{3})[\s\-\.]?([0-9]{4})$'
    return bool(re.match(pattern, phone))

# Test it
print(is_valid_us_phone("(555) 123-4567"))  # True
print(is_valid_us_phone("555-123-4567"))    # True
print(is_valid_us_phone("555123"))          # False

Pros:

  • Fast and free
  • No external dependencies
  • Works offline

Cons:

  • Only checks format, not if the number actually exists
  • Doesn't detect VoIP or temporary numbers
  • Needs different regex patterns for each country
  • Can't identify line type (mobile vs landline)

Verdict: Good for basic input sanitization, but not enough to prevent fraud.

Method 2: Google's libphonenumber Library

Google's phonenumbers library is the gold standard for phone number parsing:

import phonenumbers
from phonenumbers import carrier, geocoder, timezone

def validate_phone_detailed(phone: str, country_code: str = "US") -> dict:
    """
    Comprehensive validation using Google's libphonenumber.
    Returns detailed information about the phone number.
    """
    try:
        # Parse the number
        parsed = phonenumbers.parse(phone, country_code)
        
        # Validate it
        is_valid = phonenumbers.is_valid_number(parsed)
        is_possible = phonenumbers.is_possible_number(parsed)
        
        # Get additional info
        number_type = phonenumbers.number_type(parsed)
        carrier_name = carrier.name_for_number(parsed, "en")
        location = geocoder.description_for_number(parsed, "en")
        timezones = timezone.time_zones_for_number(parsed)
        
        return {
            "valid": is_valid,
            "possible": is_possible,
            "number_type": number_type,
            "carrier": carrier_name,
            "location": location,
            "timezones": timezones,
            "formatted": phonenumbers.format_number(
                parsed, 
                phonenumbers.PhoneNumberFormat.INTERNATIONAL
            )
        }
    except phonenumbers.NumberParseException:
        return {"valid": False, "error": "Could not parse number"}

# Install first: pip install phonenumbers

# Test it
result = validate_phone_detailed("+1-555-123-4567")
print(result)
# Output:
# {
#   'valid': True,
#   'possible': True,
#   'number_type': 1,  # Mobile
#   'carrier': 'Verizon',
#   'location': 'United States',
#   'timezones': ('America/New_York',),
#   'formatted': '+1 555-123-4567'
# }

Pros:

  • Extremely accurate for format validation
  • Supports 200+ countries
  • Identifies number type (mobile, landline, VoIP)
  • Free and open source

Cons:

  • Can't verify if a number is currently active
  • Carrier data is often outdated
  • No fraud detection capabilities
  • Large library size (slow imports)

Verdict: Excellent for format validation, but doesn't catch disconnected or fake numbers.

Method 3: Real-Time API Validation (Recommended)

For production applications, you need real-time validation that checks if the number is actually reachable. This is where APIs like Payloadic's Phone Validator come in:

import requests

def validate_phone_api(phone: str, api_key: str) -> dict:
    """
    Validate phone number using Payloadic Phone Validator API.
    Provides real-time verification and fraud detection.
    """
    url = "https://phone-validator3.p.rapidapi.com/validate"
    
    headers = {
        "X-RapidAPI-Key": api_key,
        "X-RapidAPI-Host": "phone-validator3.p.rapidapi.com"
    }
    
    params = {
        "number": phone
    }
    
    try:
        response = requests.get(url, headers=headers, params=params, timeout=5)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        return {"error": str(e)}

# Usage
api_key = "your_rapidapi_key_here"
result = validate_phone_api("+1-555-123-4567", api_key)

print(result)
# Output:
# {
#   "valid": true,
#   "number": "+15551234567",
#   "local_format": "555-123-4567",
#   "international_format": "+1 555-123-4567",
#   "country_code": "US",
#   "country_name": "United States",
#   "location": "California",
#   "carrier": "Verizon Wireless",
#   "line_type": "mobile",
#   "is_voip": false,
#   "is_valid": true
# }

Pros:

  • Checks if number is currently active
  • Detects VoIP and temporary numbers
  • Identifies line type (critical for SMS delivery)
  • Regular database updates
  • Fraud detection features

Cons:

  • Requires API key (get one at RapidAPI)
  • Costs per request (though very affordable)
  • Requires internet connection

Verdict: Best option for production apps where data quality matters.

Building a Complete Validation Pipeline

Here's a production-ready function that combines multiple approaches:

import re
import requests
from typing import Dict, Tuple

class PhoneValidator:
    def __init__(self, api_key: str = None):
        self.api_key = api_key
    
    def quick_format_check(self, phone: str) -> bool:
        """Fast regex check before hitting the API."""
        # Remove common formatting characters
        cleaned = re.sub(r'[\s\-\.\(\)]', '', phone)
        # Check if it looks like a valid number
        return bool(re.match(r'^\+?[1-9]\d{6,14}$', cleaned))
    
    def validate(self, phone: str, strict: bool = True) -> Tuple[bool, Dict]:
        """
        Two-stage validation:
        1. Quick format check (free, instant)
        2. API validation (paid, authoritative)
        """
        # Stage 1: Format check
        if not self.quick_format_check(phone):
            return False, {"error": "Invalid format"}
        
        # Stage 2: API validation (only if API key provided and strict mode)
        if strict and self.api_key:
            result = self._api_validate(phone)
            
            if result.get("error"):
                return False, result
            
            # Block VoIP numbers
            if result.get("is_voip"):
                return False, {"error": "VoIP numbers not allowed"}
            
            # Block landlines if you only want mobile
            if result.get("line_type") == "landline":
                return False, {"error": "Mobile number required"}
            
            return result.get("is_valid", False), result
        
        # If no API key, just return format check result
        return True, {"validated": "format_only"}
    
    def _api_validate(self, phone: str) -> Dict:
        """Internal method for API calls."""
        url = "https://phone-validator3.p.rapidapi.com/validate"
        headers = {
            "X-RapidAPI-Key": self.api_key,
            "X-RapidAPI-Host": "phone-validator3.p.rapidapi.com"
        }
        try:
            response = requests.get(
                url, 
                headers=headers, 
                params={"number": phone},
                timeout=5
            )
            response.raise_for_status()
            return response.json()
        except requests.RequestException as e:
            return {"error": str(e)}

# Usage in your signup flow
validator = PhoneValidator(api_key="your_api_key")

# Validate user input
phone = input("Enter your phone number: ")
is_valid, details = validator.validate(phone, strict=True)

if is_valid:
    print(f"✓ Valid number: {details.get('international_format')}")
    # Save to database and send verification SMS
else:
    print(f"✗ Invalid: {details.get('error')}")
    # Show error to user

Integration with Popular Frameworks

Flask Example

from flask import Flask, request, jsonify
from phone_validator import PhoneValidator

app = Flask(__name__)
validator = PhoneValidator(api_key="your_api_key")

@app.route('/api/validate-phone', methods=['POST'])
def validate_phone():
    phone = request.json.get('phone')
    
    if not phone:
        return jsonify({"error": "Phone number required"}), 400
    
    is_valid, details = validator.validate(phone, strict=True)
    
    if is_valid:
        return jsonify({
            "valid": True,
            "formatted": details.get("international_format"),
            "carrier": details.get("carrier"),
            "line_type": details.get("line_type")
        })
    else:
        return jsonify({
            "valid": False,
            "error": details.get("error")
        }), 400

Django Example

# validators.py
from django.core.exceptions import ValidationError
from phone_validator import PhoneValidator

validator = PhoneValidator(api_key="your_api_key")

def validate_phone_number(value):
    is_valid, details = validator.validate(value, strict=True)
    if not is_valid:
        raise ValidationError(
            f"Invalid phone number: {details.get('error')}",
            code='invalid_phone'
        )
    return value

# models.py
from django.db import models
from .validators import validate_phone_number

class User(models.Model):
    phone = models.CharField(
        max_length=20,
        validators=[validate_phone_number]
    )

Best Practices

  1. Validate early: Check the phone number as soon as the user enters it (client-side + server-side)
  2. Cache results: Store validation results to avoid duplicate API calls
  3. Handle errors gracefully: API calls can fail; have a fallback strategy
  4. Rate limiting: Implement rate limits to prevent abuse
  5. Progressive validation: Do cheap checks first (regex), then expensive ones (API)
  6. Log blocked numbers: Track patterns to improve your fraud detection

Cost Optimization Tips

API calls cost money. Here's how to minimize costs:

import hashlib
from functools import lru_cache

class CachedPhoneValidator(PhoneValidator):
    @lru_cache(maxsize=10000)
    def validate_cached(self, phone: str, strict: bool = True):
        """Cache validation results to avoid duplicate API calls."""
        return self.validate(phone, strict)

# Use the cached version
validator = CachedPhoneValidator(api_key="your_api_key")

This caches results in memory. For production, use Redis:

import redis
import json

class RedisPhoneValidator(PhoneValidator):
    def __init__(self, api_key: str, redis_client: redis.Redis):
        super().__init__(api_key)
        self.redis = redis_client
    
    def validate(self, phone: str, strict: bool = True):
        # Check cache first
        cache_key = f"phone_validation:{phone}"
        cached = self.redis.get(cache_key)
        
        if cached:
            return True, json.loads(cached)
        
        # Validate and cache
        is_valid, details = super().validate(phone, strict)
        
        if is_valid:
            # Cache for 30 days
            self.redis.setex(cache_key, 2592000, json.dumps(details))
        
        return is_valid, details

Conclusion

Phone validation is essential for any application that collects user phone numbers. While basic regex checks are better than nothing, production apps should use real-time API validation to ensure data quality and prevent fraud.

The Payloadic Phone Validator API offers:

  • Real-time verification of 200+ countries
  • Line type detection (mobile, landline, VoIP)
  • Carrier identification
  • Fraud detection
  • Simple integration via RapidAPI

Ready to get started? Try the Phone Validator API with a free tier on RapidAPI.

Additional Resources

Tags:

pythonphone validationfraud preventionuser verificationAPI integration

Ready to Try It Yourself?

Get started with Payloadic APIs and integrate phone validation or ZIP code lookup into your application today.