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.
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:
- Wrong format: Missing digits, incorrect country code, or invalid area code
- Disconnected: The number was valid once but is no longer in service
- VoIP/temporary: Numbers from services like Google Voice or burner apps
- Landline: Can't receive SMS messages (if you're sending verification codes)
- 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
- Validate early: Check the phone number as soon as the user enters it (client-side + server-side)
- Cache results: Store validation results to avoid duplicate API calls
- Handle errors gracefully: API calls can fail; have a fallback strategy
- Rate limiting: Implement rate limits to prevent abuse
- Progressive validation: Do cheap checks first (regex), then expensive ones (API)
- 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:
Ready to Try It Yourself?
Get started with Payloadic APIs and integrate phone validation or ZIP code lookup into your application today.