Carrier Intelligence: Understanding Mobile Network Information
Leverage carrier intelligence for enhanced phone validation, fraud detection, and user experience optimization.
Table of Contents
Table of Contents
Carrier Intelligence: Understanding Mobile Network Information
Carrier intelligence provides valuable insights into mobile network characteristics, user behavior, and service capabilities. Leveraging this information enhances validation accuracy and enables sophisticated fraud detection.
Carrier Intelligence Overview
Business Impact
- User experience enhancement: By identifying a user's carrier and network type (e.g., 5G vs. LTE), businesses can optimize content delivery, such as adjusting video bitrates or pre-loading assets based on expected bandwidth.
- Operational efficiency gains: Automated carrier lookups reduce the need for manual verification of customer data and streamline communication workflows by ensuring messages are sent via the most compatible network routes.
- Cost optimization opportunities: Identifying "landline" vs. "mobile" numbers or "inactive" status before sending SMS campaigns prevents wasted spend on undeliverable messages.
- Risk mitigation strategies: Real-time detection of SIM-swapping or recent porting activity allows security teams to trigger additional authentication steps during high-risk transactions.
Carrier Intelligence Overview
Carrier intelligence systems analyze mobile network data to provide real-time insights about phone numbers, carriers, and network characteristics. This technology enables businesses to:
- Validate phone numbers with carrier-level accuracy by checking the actual network status rather than just relying on syntax or regex patterns.
- Detect fraud through network behavior analysis, such as identifying numbers that have been recently ported or are currently in a roaming state, which are often markers for identity theft.
- Optimize user experience by understanding network capabilities, allowing for specialized routing or customized UI elements based on the carrier's specific services (like RCS support).
- Reduce costs by preventing invalid transactions and avoiding the high fees associated with sending messages to international or premium-rate numbers incorrectly identified as local.
Key Components
HLR (Home Location Register) Lookups
The HLR is the central database that contains details of each mobile phone subscriber authorized to use a GSM core network. Lookups provide:
- Real-time carrier identification: Confirms which network currently "owns" the subscriber.
- Network status verification: Determines if the phone is currently switched on and registered.
- Roaming and portability detection: Checks if the user is outside their home country or has moved their number to a different provider.
MCC/MNC Database
Mobile Country Codes (MCC) and Mobile Network Codes (MNC) are the foundation of global telephony:
- MCC/MNC Mapping: Essential for identifying the geographical origin and specific operator of a device.
- Carrier Name Resolution: Converts technical codes into human-readable brand names (e.g., 310-260 becomes Verizon).
- Network Technology Detection: Identifies whether the carrier supports specific protocols like 5G NR or VoLTE.
Fraud Detection Engine
A sophisticated layer that sits atop raw data to interpret risk:
- Velocity analysis: Tracks how many times a number is queried across a timeframe to spot botnets or automated account creation.
- Pattern recognition: Identifies clusters of numbers being activated or used in a way that suggests a coordinated attack.
- Risk scoring algorithms: Aggregates data points (location, roaming, porting history) into a single actionable number.
HLR Lookup System for Real-Time Carrier Intelligence
// Production-ready HLR lookup system for carrier intelligence and phone validation
interface HLRRequest {
phoneNumber: string
countryCode?: string
includeLocation?: boolean
includeRoaming?: boolean
timeout?: number
}
interface HLRResponse {
phoneNumber: string
imsi?: string
mcc?: string
mnc?: string
msisdn?: string
status: 'active' | 'inactive' | 'invalid' | 'ported' | 'unknown'
carrier: {
name: string
country: string
network: string
mcc: string
mnc: string
}
location?: {
country: string
region: string
city: string
coordinates?: {
latitude: number
longitude: number
}
}
roaming?: {
isRoaming: boolean
originalCarrier?: string
currentCarrier?: string
}
ported?: {
isPorted: boolean
originalCarrier?: string
portedDate?: number
}
lastSeen?: number
firstSeen?: number
}
interface CarrierDatabaseEntry {
mcc: string
mnc: string
country: string
carrier: string
network: string
bands: string[]
technologies: string[]
roamingAgreements: string[]
lastUpdated: number
}
class HLRIntelligenceSystem {
private carrierDatabase: Map<string, CarrierDatabaseEntry> = new Map()
private cache: Map<string, { response: HLRResponse; timestamp: number }> = new Map()
private requestQueue: Array<{ request: HLRRequest; resolve: Function; reject: Function }> = []
private isProcessingQueue = false
private rateLimitDelay = 100 // ms between requests
private maxConcurrentRequests = 5
constructor() {
this.initializeCarrierDatabase()
this.startQueueProcessor()
}
async performHLRLookup(request: HLRRequest): Promise<HLRResponse> {
const cacheKey = this.generateCacheKey(request)
// Check cache first (5 minutes TTL)
const cached = this.getCachedResponse(cacheKey)
if (cached) {
return cached
}
return new Promise((resolve, reject) => {
this.requestQueue.push({ request, resolve, reject })
this.processQueue()
})
}
private generateCacheKey(request: HLRRequest): string {
return Buffer.from(`${request.phoneNumber}-${request.countryCode || ''}`).toString('base64')
}
private getCachedResponse(cacheKey: string): HLRResponse | null {
const cached = this.cache.get(cacheKey)
if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) {
return cached.response
}
if (cached) {
this.cache.delete(cacheKey)
}
return null
}
private async processQueue(): Promise<void> {
if (this.isProcessingQueue || this.requestQueue.length === 0) {
return
}
this.isProcessingQueue = true
while (this.requestQueue.length > 0) {
const batch = this.requestQueue.splice(0, this.maxConcurrentRequests)
const batchPromises = batch.map(async ({ request, resolve, reject }) => {
try {
const response = await this.executeHLRRequest(request)
this.cacheResponse(request, response)
resolve(response)
} catch (error) {
reject(error)
}
})
await Promise.allSettled(batchPromises)
// Rate limiting delay
if (this.requestQueue.length > 0) {
await new Promise(resolve => setTimeout(resolve, this.rateLimitDelay))
}
}
this.isProcessingQueue = false
}
private async executeHLRRequest(request: HLRRequest): Promise<HLRResponse> {
// In production, this would connect to HLR providers like:
// - TMT Analysis, OpenCellID, Combain, etc.
// For demo, simulate HLR response
const phoneNumber = this.normalizePhoneNumber(request.phoneNumber, request.countryCode)
if (!this.isValidPhoneNumber(phoneNumber)) {
throw new Error('Invalid phone number format')
}
// Simulate HLR lookup delay
await new Promise(resolve => setTimeout(resolve, 200 + Math.random() * 300))
return this.generateMockHLRResponse(phoneNumber, request)
}
private normalizePhoneNumber(phoneNumber: string, countryCode?: string): string {
// Remove all non-digit characters
let normalized = phoneNumber.replace(/D/g, '')
// Add country code if not present
if (countryCode && !normalized.startsWith(countryCode)) {
normalized = countryCode + normalized
}
return normalized
}
private isValidPhoneNumber(phoneNumber: string): boolean {
// Basic validation - should be 10-15 digits
return phoneNumber.length >= 10 && phoneNumber.length <= 15 && /^d+$/.test(phoneNumber)
}
private generateMockHLRResponse(phoneNumber: string, request: HLRRequest): HLRResponse {
const mockCarriers = [
{ mcc: '250', mnc: '01', country: 'RU', carrier: 'MTS', network: 'MTS Russia' },
{ mcc: '250', mnc: '02', country: 'RU', carrier: 'MegaFon', network: 'MegaFon Russia' },
{ mcc: '250', mnc: '20', country: 'RU', carrier: 'Tele2', network: 'Tele2 Russia' },
{ mcc: '250', mnc: '99', country: 'RU', carrier: 'Beeline', network: 'Beeline Russia' },
{ mcc: '310', mnc: '120', country: 'US', carrier: 'T-Mobile', network: 'T-Mobile USA' },
{ mcc: '310', mnc: '260', country: 'US', carrier: 'Verizon', network: 'Verizon Wireless' }
]
// Select carrier based on phone number prefix (simplified)
const prefix = phoneNumber.substring(0, 3)
let selectedCarrier = mockCarriers[0]
if (prefix === '791' || prefix === '792') selectedCarrier = mockCarriers[0] // MTS
else if (prefix === '793' || prefix === '798') selectedCarrier = mockCarriers[1] // MegaFon
else if (prefix === '795') selectedCarrier = mockCarriers[2] // Tele2
else if (prefix === '796' || prefix === '790') selectedCarrier = mockCarriers[3] // Beeline
else if (phoneNumber.startsWith('1')) selectedCarrier = mockCarriers[4] // T-Mobile
else selectedCarrier = mockCarriers[5] // Verizon
const isRoaming = Math.random() < 0.1 // 10% chance of roaming
const isPorted = Math.random() < 0.15 // 15% chance of ported number
const response: HLRResponse = {
phoneNumber: request.phoneNumber,
imsi: `310150${Math.random().toString().substring(2, 15)}`,
mcc: selectedCarrier.mcc,
mnc: selectedCarrier.mnc,
status: Math.random() < 0.85 ? 'active' : 'inactive', // 85% active
carrier: {
name: selectedCarrier.carrier,
country: selectedCarrier.country,
network: selectedCarrier.network,
mcc: selectedCarrier.mcc,
mnc: selectedCarrier.mnc
}
}
// Add location data if requested
if (request.includeLocation) {
response.location = {
country: selectedCarrier.country === 'RU' ? 'Russia' : 'United States',
region: selectedCarrier.country === 'RU' ? 'Moscow' : 'California',
city: selectedCarrier.country === 'RU' ? 'Moscow' : 'Los Angeles',
coordinates: selectedCarrier.country === 'RU'
? { latitude: 55.7558, longitude: 37.6176 }
: { latitude: 34.0522, longitude: -118.2437 }
}
}
// Add roaming data if requested
if (request.includeRoaming) {
response.roaming = {
isRoaming,
originalCarrier: isRoaming ? selectedCarrier.carrier : undefined,
currentCarrier: isRoaming ? 'Roaming Partner' : selectedCarrier.carrier
}
}
// Add ported information
if (isPorted) {
response.ported = {
isPorted: true,
originalCarrier: 'Original Carrier',
portedDate: Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000
}
}
// Add timestamps
response.lastSeen = Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000 // Within last week
response.firstSeen = Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000 // Within last year
return response
}
private initializeCarrierDatabase(): void {
// In production, load comprehensive carrier database
// For demo, populate with sample data
const carriers: CarrierDatabaseEntry[] = [
{
mcc: '250',
mnc: '01',
country: 'RU',
carrier: 'MTS',
network: 'MTS Russia',
bands: ['GSM 900', 'GSM 1800', 'UMTS 2100', 'LTE 1800', 'LTE 2600'],
technologies: ['2G', '3G', '4G', 'VoLTE'],
roamingAgreements: ['US', 'EU', 'CN', 'IN'],
lastUpdated: Date.now()
},
{
mcc: '310',
mnc: '120',
country: 'US',
carrier: 'T-Mobile',
network: 'T-Mobile USA',
bands: ['GSM 1900', 'UMTS 1900', 'LTE 700', 'LTE 1900'],
technologies: ['3G', '4G', '5G'],
roamingAgreements: ['CA', 'MX', 'EU'],
lastUpdated: Date.now()
}
]
carriers.forEach(carrier => {
const key = `${carrier.mcc}-${carrier.mnc}`
this.carrierDatabase.set(key, carrier)
})
console.log(`Carrier database initialized with ${carriers.length} entries`)
}
private startQueueProcessor(): void {
// Start background queue processor
setInterval(() => {
if (this.requestQueue.length > 0 && !this.isProcessingQueue) {
this.processQueue()
}
}, 100)
}
private cacheResponse(request: HLRRequest, response: HLRResponse): void {
const cacheKey = this.generateCacheKey(request)
this.cache.set(cacheKey, {
response,
timestamp: Date.now()
})
// Clean old cache entries (keep only last 10000)
if (this.cache.size > 10000) {
const entries = Array.from(this.cache.entries())
const oldestKey = entries.sort(([, a], [, b]) => a.timestamp - b.timestamp)[0][0]
this.cache.delete(oldestKey)
}
}
// Batch HLR lookups for multiple numbers
async performBatchHLRLookup(requests: HLRRequest[]): Promise<Map<string, HLRResponse>> {
const results = new Map<string, HLRResponse>()
// Process in batches of 10
const batchSize = 10
for (let i = 0; i < requests.length; i += batchSize) {
const batch = requests.slice(i, i + batchSize)
const batchPromises = batch.map(async (request) => {
try {
const response = await this.performHLRLookup(request)
return { phoneNumber: request.phoneNumber, response }
} catch (error) {
console.error(`Batch HLR lookup failed for ${request.phoneNumber}:`, error)
return { phoneNumber: request.phoneNumber, response: null }
}
})
const batchResults = await Promise.all(batchPromises)
batchResults.forEach(({ phoneNumber, response }) => {
if (response) {
results.set(phoneNumber, response)
}
})
// Small delay between batches
if (i + batchSize < requests.length) {
await new Promise(resolve => setTimeout(resolve, 500))
}
}
return results
}
// Get carrier statistics
getCarrierStatistics(): {
totalLookups: number
uniqueCarriers: number
activeSubscribers: number
roamingSubscribers: number
portedNumbers: number
topCarriers: Array<{ carrier: string; count: number }>
} {
const responses = Array.from(this.cache.values()).map(c => c.response)
const total = responses.length
if (total === 0) {
return {
totalLookups: 0,
uniqueCarriers: 0,
activeSubscribers: 0,
roamingSubscribers: 0,
portedNumbers: 0,
topCarriers: []
}
}
const carriers = responses.map(r => r.carrier.name)
const uniqueCarriers = new Set(carriers).size
const activeSubscribers = responses.filter(r => r.status === 'active').length
const roamingSubscribers = responses.filter(r => r.roaming?.isRoaming).length
const portedNumbers = responses.filter(r => r.ported?.isPorted).length
// Count carrier occurrences
const carrierCounts = new Map<string, number>()
carriers.forEach(carrier => {
const count = carrierCounts.get(carrier) || 0
carrierCounts.set(carrier, count + 1)
})
const topCarriers = Array.from(carrierCounts.entries())
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([carrier, count]) => ({ carrier, count }))
return {
totalLookups: total,
uniqueCarriers,
activeSubscribers,
roamingSubscribers,
portedNumbers,
topCarriers
}
}
}
// Initialize HLR intelligence system
const hlrIntelligenceSystem = new HLRIntelligenceSystem()
// HLR lookup endpoint
app.post('/api/hlr/lookup', async (req, res) => {
try {
const { phoneNumber, countryCode, includeLocation, includeRoaming } = req.body
if (!phoneNumber) {
return res.status(400).json({ error: 'phoneNumber required' })
}
const request: HLRRequest = {
phoneNumber,
countryCode,
includeLocation,
includeRoaming,
timeout: 10000
}
const response = await hlrIntelligenceSystem.performHLRLookup(request)
res.json({
hlr: response,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('HLR lookup error:', error)
res.status(500).json({ error: 'HLR lookup failed' })
}
})
// Batch HLR lookup endpoint
app.post('/api/hlr/lookup-batch', async (req, res) => {
try {
const { phoneNumbers, countryCode, includeLocation, includeRoaming } = req.body
if (!Array.isArray(phoneNumbers)) {
return res.status(400).json({ error: 'phoneNumbers array required' })
}
const requests: HLRRequest[] = phoneNumbers.map(phoneNumber => ({
phoneNumber,
countryCode,
includeLocation,
includeRoaming,
timeout: 10000
}))
const results = await hlrIntelligenceSystem.performBatchHLRLookup(requests)
res.json({
results: Object.fromEntries(results.entries()),
count: results.size,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('Batch HLR lookup error:', error)
res.status(500).json({ error: 'Batch HLR lookup failed' })
}
})
// HLR statistics endpoint
app.get('/api/hlr/statistics', (req, res) => {
try {
const stats = hlrIntelligenceSystem.getCarrierStatistics()
res.json({
statistics: stats,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('HLR statistics error:', error)
res.status(500).json({ error: 'Statistics retrieval failed' })
}
})
console.log('HLR Intelligence System initialized')Carrier Detection and Intelligence Engine
// Advanced carrier intelligence engine for phone number analysis
interface CarrierIntelligenceConfig {
enableHLRLookup: boolean
enableNetworkAnalysis: boolean
enableFraudDetection: boolean
cacheResults: boolean
cacheDuration: number
maxRetries: number
}
interface PhoneNumberAnalysis {
phoneNumber: string
normalizedNumber: string
country: {
code: string
name: string
region: string
}
carrier: {
name: string
mcc: string
mnc: string
networkType: 'gsm' | 'cdma' | 'lte' | '5g' | 'unknown'
bands: string[]
}
validation: {
isValid: boolean
format: 'e164' | 'national' | 'international' | 'invalid'
checksumValid: boolean
lengthValid: boolean
}
intelligence: {
isActive: boolean
isRoaming: boolean
isPorted: boolean
lastSeen: number
riskScore: number
confidence: number
}
network: {
technology: string[]
bands: string[]
roamingAgreements: string[]
coverage: {
area: string[]
quality: 'excellent' | 'good' | 'fair' | 'poor'
}
}
fraudIndicators: {
velocityCheck: boolean
patternAnalysis: boolean
blacklisted: boolean
suspiciousActivity: boolean
riskFactors: string[]
}
}
interface FraudDetectionResult {
phoneNumber: string
riskScore: number // 0-100
riskLevel: 'low' | 'medium' | 'high' | 'critical'
indicators: string[]
recommendations: string[]
blocked: boolean
requiresReview: boolean
}
class CarrierIntelligenceEngine {
private config: CarrierIntelligenceConfig
private hlrSystem: HLRIntelligenceSystem
private numberCache: Map<string, PhoneNumberAnalysis> = new Map()
private fraudCache: Map<string, FraudDetectionResult> = new Map()
constructor(config: CarrierIntelligenceConfig) {
this.config = config
this.hlrSystem = new HLRIntelligenceSystem()
}
async analyzePhoneNumber(phoneNumber: string, countryCode?: string): Promise<PhoneNumberAnalysis> {
const normalizedNumber = this.normalizePhoneNumber(phoneNumber, countryCode)
// Check cache first
if (this.config.cacheResults) {
const cached = this.getCachedAnalysis(normalizedNumber)
if (cached) {
return cached
}
}
try {
// Perform comprehensive phone number analysis
const [basicInfo, carrierInfo, validationInfo, intelligenceInfo] = await Promise.all([
this.getBasicNumberInfo(normalizedNumber),
this.getCarrierInformation(normalizedNumber),
this.validatePhoneNumberFormat(normalizedNumber),
this.gatherIntelligenceData(normalizedNumber)
])
const analysis: PhoneNumberAnalysis = {
phoneNumber,
normalizedNumber,
...basicInfo,
carrier: carrierInfo,
validation: validationInfo,
intelligence: intelligenceInfo.intelligence,
network: intelligenceInfo.network,
fraudIndicators: intelligenceInfo.fraudIndicators
}
// Cache result
if (this.config.cacheResults) {
this.cacheAnalysis(normalizedNumber, analysis)
}
return analysis
} catch (error) {
console.error('Phone number analysis failed:', error)
// Return basic analysis on error
return {
phoneNumber,
normalizedNumber,
country: { code: countryCode || 'US', name: 'Unknown', region: 'Unknown' },
carrier: {
name: 'Unknown',
mcc: '000',
mnc: '00',
networkType: 'unknown',
bands: []
},
validation: {
isValid: false,
format: 'invalid',
checksumValid: false,
lengthValid: false
},
intelligence: {
isActive: false,
isRoaming: false,
isPorted: false,
lastSeen: 0,
riskScore: 100,
confidence: 0
},
network: {
technology: [],
bands: [],
roamingAgreements: [],
coverage: {
area: [],
quality: 'poor'
}
},
fraudIndicators: {
velocityCheck: false,
patternAnalysis: false,
blacklisted: true,
suspiciousActivity: true,
riskFactors: ['Analysis failed']
}
}
}
}
private normalizePhoneNumber(phoneNumber: string, countryCode?: string): string {
let normalized = phoneNumber.replace(/D/g, '')
// Add country code if not present and we have it
if (countryCode && !normalized.startsWith(countryCode)) {
normalized = countryCode + normalized
}
return normalized
}
private async getBasicNumberInfo(phoneNumber: string): Promise<{
country: PhoneNumberAnalysis['country']
}> {
// In production, use comprehensive number database
// For demo, use simple country detection based on prefixes
const countryPrefixes: Record<string, { code: string; name: string; region: string }> = {
'1': { code: 'US', name: 'United States', region: 'North America' },
'7': { code: 'RU', name: 'Russia', region: 'Europe' },
'44': { code: 'GB', name: 'United Kingdom', region: 'Europe' },
'33': { code: 'FR', name: 'France', region: 'Europe' },
'49': { code: 'DE', name: 'Germany', region: 'Europe' },
'39': { code: 'IT', name: 'Italy', region: 'Europe' },
'34': { code: 'ES', name: 'Spain', region: 'Europe' },
'31': { code: 'NL', name: 'Netherlands', region: 'Europe' }
}
let countryCode = '1' // Default to US
for (let i = 1; i <= 3; i++) {
const prefix = phoneNumber.substring(0, i)
if (countryPrefixes[prefix]) {
countryCode = prefix
break
}
}
return {
country: countryPrefixes[countryCode] || countryPrefixes['1']
}
}
private async getCarrierInformation(phoneNumber: string): Promise<PhoneNumberAnalysis['carrier']> {
if (!this.config.enableHLRLookup) {
return {
name: 'Unknown',
mcc: '000',
mnc: '00',
networkType: 'unknown',
bands: []
}
}
try {
const hlrResponse = await this.hlrSystem.performHLRLookup({
phoneNumber,
includeLocation: false,
includeRoaming: false
})
return {
name: hlrResponse.carrier.name,
mcc: hlrResponse.carrier.mcc,
mnc: hlrResponse.carrier.mnc,
networkType: this.determineNetworkType(hlrResponse.carrier.mcc, hlrResponse.carrier.mnc),
bands: this.getCarrierBands(hlrResponse.carrier.mcc, hlrResponse.carrier.mnc)
}
} catch (error) {
console.error('Carrier lookup failed:', error)
return {
name: 'Unknown',
mcc: '000',
mnc: '00',
networkType: 'unknown',
bands: []
}
}
}
private validatePhoneNumberFormat(phoneNumber: string): PhoneNumberAnalysis['validation'] {
const length = phoneNumber.length
const isValidLength = length >= 10 && length <= 15
// Basic checksum validation (simplified)
const checksumValid = this.validateChecksum(phoneNumber)
return {
isValid: isValidLength && checksumValid,
format: length > 10 ? 'international' : 'national',
checksumValid,
lengthValid: isValidLength
}
}
private async gatherIntelligenceData(phoneNumber: string): Promise<{
intelligence: PhoneNumberAnalysis['intelligence']
network: PhoneNumberAnalysis['network']
fraudIndicators: PhoneNumberAnalysis['fraudIndicators']
}> {
const intelligence: PhoneNumberAnalysis['intelligence'] = {
isActive: Math.random() > 0.1, // 90% active
isRoaming: Math.random() > 0.9, // 10% roaming
isPorted: Math.random() > 0.85, // 15% ported
lastSeen: Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000, // Within last 30 days
riskScore: Math.floor(Math.random() * 100),
confidence: 75 + Math.floor(Math.random() * 25) // 75-100%
}
const network: PhoneNumberAnalysis['network'] = {
technology: ['4G', 'VoLTE'],
bands: ['LTE 1800', 'LTE 2600'],
roamingAgreements: ['US', 'EU', 'APAC'],
coverage: {
area: ['Nationwide', 'Urban', 'Suburban'],
quality: Math.random() > 0.5 ? 'excellent' : 'good'
}
}
const fraudIndicators: PhoneNumberAnalysis['fraudIndicators'] = {
velocityCheck: Math.random() > 0.95, // 5% suspicious velocity
patternAnalysis: Math.random() > 0.98, // 2% suspicious pattern
blacklisted: Math.random() > 0.99, // 1% blacklisted
suspiciousActivity: Math.random() > 0.97, // 3% suspicious
riskFactors: intelligence.riskScore > 70 ? ['High risk score'] : []
}
return { intelligence, network, fraudIndicators }
}
private determineNetworkType(mcc: string, mnc: string): PhoneNumberAnalysis['carrier']['networkType'] {
// In production, use carrier database to determine network type
// For demo, return based on MCC
if (mcc === '310' || mcc === '311' || mcc === '312') {
return 'cdma'
}
return 'gsm'
}
private getCarrierBands(mcc: string, mnc: string): string[] {
// In production, use carrier database to get supported bands
// For demo, return common bands
return ['GSM 900', 'GSM 1800', 'UMTS 2100', 'LTE 1800', 'LTE 2600']
}
private validateChecksum(phoneNumber: string): boolean {
// Simplified checksum validation
// In production, implement proper checksum algorithms per country
if (phoneNumber.length < 10) return false
// Simple modulo check (not real checksum)
const digits = phoneNumber.split('').map(Number)
const sum = digits.reduce((acc, digit, index) => acc + digit * (index + 1), 0)
return sum % 10 === 0
}
private getCachedAnalysis(phoneNumber: string): PhoneNumberAnalysis | null {
const cached = this.numberCache.get(phoneNumber)
if (cached && Date.now() - this.config.cacheDuration < cached.intelligence.lastSeen) {
return cached
}
if (cached) {
this.numberCache.delete(phoneNumber)
}
return null
}
private cacheAnalysis(phoneNumber: string, analysis: PhoneNumberAnalysis): void {
this.numberCache.set(phoneNumber, analysis)
// Maintain cache size
if (this.numberCache.size > 50000) {
const entries = Array.from(this.numberCache.entries())
const oldestKey = entries.sort(([, a], [, b]) => a.intelligence.lastSeen - b.intelligence.lastSeen)[0][0]
this.numberCache.delete(oldestKey)
}
}
// Fraud detection analysis
async detectFraud(phoneNumber: string): Promise<FraudDetectionResult> {
const cached = this.fraudCache.get(phoneNumber)
if (cached && Date.now() - 24 * 60 * 60 * 1000 < Date.now()) { // 24 hour cache
return cached
}
try {
const analysis = await this.analyzePhoneNumber(phoneNumber)
const indicators: string[] = []
const recommendations: string[] = []
// Analyze risk indicators
if (analysis.fraudIndicators.blacklisted) {
indicators.push('Number is blacklisted')
recommendations.push('Block transaction immediately')
}
if (analysis.fraudIndicators.velocityCheck) {
indicators.push('Suspicious velocity pattern')
recommendations.push('Require additional verification')
}
if (analysis.fraudIndicators.suspiciousActivity) {
indicators.push('Suspicious activity detected')
recommendations.push('Manual review required')
}
if (analysis.intelligence.riskScore > 80) {
indicators.push('High risk score')
recommendations.push('Enhanced security measures required')
}
if (analysis.intelligence.isRoaming) {
indicators.push('International roaming detected')
recommendations.push('Verify location consistency')
}
const riskScore = Math.max(
analysis.intelligence.riskScore,
analysis.fraudIndicators.blacklisted ? 100 : 0,
analysis.fraudIndicators.velocityCheck ? 85 : 0,
analysis.fraudIndicators.suspiciousActivity ? 90 : 0
)
const riskLevel = riskScore > 90 ? 'critical' :
riskScore > 70 ? 'high' :
riskScore > 40 ? 'medium' : 'low'
const result: FraudDetectionResult = {
phoneNumber,
riskScore,
riskLevel,
indicators,
recommendations,
blocked: riskScore > 95,
requiresReview: riskScore > 70
}
this.fraudCache.set(phoneNumber, result)
return result
} catch (error) {
console.error('Fraud detection failed:', error)
return {
phoneNumber,
riskScore: 100,
riskLevel: 'critical',
indicators: ['Analysis failed'],
recommendations: ['Block transaction'],
blocked: true,
requiresReview: true
}
}
}
// Batch analysis for multiple numbers
async analyzePhoneNumbersBatch(phoneNumbers: string[]): Promise<Map<string, PhoneNumberAnalysis>> {
const results = new Map<string, PhoneNumberAnalysis>()
// Process in batches of 20
const batchSize = 20
for (let i = 0; i < phoneNumbers.length; i += batchSize) {
const batch = phoneNumbers.slice(i, i + batchSize)
const batchPromises = batch.map(async (phoneNumber) => {
try {
const analysis = await this.analyzePhoneNumber(phoneNumber)
return { phoneNumber, analysis }
} catch (error) {
console.error(`Batch analysis failed for ${phoneNumber}:`, error)
return { phoneNumber, analysis: null }
}
})
const batchResults = await Promise.all(batchPromises)
batchResults.forEach(({ phoneNumber, analysis }) => {
if (analysis) {
results.set(phoneNumber, analysis)
}
})
// Small delay between batches
if (i + batchSize < phoneNumbers.length) {
await new Promise(resolve => setTimeout(resolve, 200))
}
}
return results
}
// Get intelligence statistics
getIntelligenceStats(): {
totalAnalyses: number
validNumbers: number
activeSubscribers: number
roamingSubscribers: number
averageRiskScore: number
topRiskFactors: Array<{ factor: string; count: number }>
} {
const analyses = Array.from(this.numberCache.values())
const total = analyses.length
if (total === 0) {
return {
totalAnalyses: 0,
validNumbers: 0,
activeSubscribers: 0,
roamingSubscribers: 0,
averageRiskScore: 0,
topRiskFactors: []
}
}
const validNumbers = analyses.filter(a => a.validation.isValid).length
const activeSubscribers = analyses.filter(a => a.intelligence.isActive).length
const roamingSubscribers = analyses.filter(a => a.intelligence.isRoaming).length
const averageRiskScore = analyses.reduce((sum, a) => sum + a.intelligence.riskScore, 0) / total
// Count risk factors
const riskFactors = new Map<string, number>()
analyses.forEach(analysis => {
analysis.fraudIndicators.riskFactors.forEach(factor => {
const count = riskFactors.get(factor) || 0
riskFactors.set(factor, count + 1)
})
})
const topRiskFactors = Array.from(riskFactors.entries())
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([factor, count]) => ({ factor, count }))
return {
totalAnalyses: total,
validNumbers,
activeSubscribers,
roamingSubscribers,
averageRiskScore,
topRiskFactors
}
}
}
// Enhanced phone number validation with carrier intelligence
class EnhancedPhoneValidator {
private carrierEngine: CarrierIntelligenceEngine
private validationCache: Map<string, {
result: PhoneNumberAnalysis
expires: number
}> = new Map()
constructor() {
const config: CarrierIntelligenceConfig = {
enableHLRLookup: true,
enableNetworkAnalysis: true,
enableFraudDetection: true,
cacheResults: true,
cacheDuration: 24 * 60 * 60 * 1000, // 24 hours
maxRetries: 3
}
this.carrierEngine = new CarrierIntelligenceEngine(config)
}
async validatePhoneNumber(phoneNumber: string, countryCode?: string): Promise<{
isValid: boolean
analysis: PhoneNumberAnalysis
fraudCheck?: FraudDetectionResult
recommendation: string
}> {
// Check cache first
const cached = this.getCachedValidation(phoneNumber)
if (cached) {
return {
isValid: cached.result.validation.isValid,
analysis: cached.result,
recommendation: this.generateRecommendation(cached.result)
}
}
try {
// Perform comprehensive analysis
const analysis = await this.carrierEngine.analyzePhoneNumber(phoneNumber, countryCode)
// Perform fraud detection if enabled
let fraudCheck: FraudDetectionResult | undefined
if (this.carrierEngine['config'].enableFraudDetection) {
fraudCheck = await this.carrierEngine.detectFraud(phoneNumber)
}
const result = {
isValid: analysis.validation.isValid && analysis.intelligence.confidence > 50,
analysis,
fraudCheck,
recommendation: this.generateRecommendation(analysis, fraudCheck)
}
// Cache result for 24 hours
this.cacheValidation(phoneNumber, result)
return result
} catch (error) {
console.error('Enhanced phone validation failed:', error)
return {
isValid: false,
analysis: {} as PhoneNumberAnalysis,
recommendation: 'Validation failed - manual review required'
}
}
}
private getCachedValidation(phoneNumber: string): PhoneNumberAnalysis | null {
const cached = this.validationCache.get(phoneNumber)
if (cached && cached.expires > Date.now()) {
return cached.result
}
if (cached) {
this.validationCache.delete(phoneNumber)
}
return null
}
private cacheValidation(phoneNumber: string, result: any): void {
this.validationCache.set(phoneNumber, {
result: result.analysis,
expires: Date.now() + 24 * 60 * 60 * 1000 // 24 hours
})
// Maintain cache size
if (this.validationCache.size > 100000) {
const entries = Array.from(this.validationCache.entries())
const oldestKey = entries.sort(([, a], [, b]) => a.expires - b.expires)[0][0]
this.validationCache.delete(oldestKey)
}
}
private generateRecommendation(
analysis: PhoneNumberAnalysis,
fraudCheck?: FraudDetectionResult
): string {
if (!analysis.validation.isValid) {
return 'Invalid phone number format - please check and retry'
}
if (analysis.intelligence.riskScore > 80) {
return 'High risk number detected - enhanced verification required'
}
if (analysis.intelligence.isRoaming) {
return 'Roaming number detected - verify international compatibility'
}
if (analysis.intelligence.isPorted) {
return 'Ported number detected - carrier information may be outdated'
}
if (fraudCheck?.blocked) {
return 'Number blocked due to fraud indicators - transaction denied'
}
if (fraudCheck?.requiresReview) {
return 'Number requires manual review before processing'
}
return 'Phone number validated successfully'
}
// Batch validation for multiple numbers
async validatePhoneNumbersBatch(phoneNumbers: string[]): Promise<Map<string, any>> {
return this.carrierEngine.analyzePhoneNumbersBatch(phoneNumbers)
}
getValidationStats(): {
totalValidations: number
validNumbers: number
highRiskNumbers: number
roamingNumbers: number
averageConfidence: number
} {
return this.carrierEngine.getIntelligenceStats()
}
}
// Initialize enhanced phone validator
const enhancedPhoneValidator = new EnhancedPhoneValidator()
// Enhanced phone validation endpoints
app.post('/api/validate-phone-enhanced', async (req, res) => {
try {
const { phoneNumber, countryCode } = req.body
if (!phoneNumber) {
return res.status(400).json({ error: 'phoneNumber required' })
}
const result = await enhancedPhoneValidator.validatePhoneNumber(phoneNumber, countryCode)
res.json({
validation: result,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('Enhanced phone validation error:', error)
res.status(500).json({ error: 'Enhanced validation failed' })
}
})
// Batch phone validation endpoint
app.post('/api/validate-phones-batch', async (req, res) => {
try {
const { phoneNumbers } = req.body
if (!Array.isArray(phoneNumbers)) {
return res.status(400).json({ error: 'phoneNumbers array required' })
}
const results = await enhancedPhoneValidator.validatePhoneNumbersBatch(phoneNumbers)
res.json({
results: Object.fromEntries(results.entries()),
count: results.size,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('Batch phone validation error:', error)
res.status(500).json({ error: 'Batch validation failed' })
}
})
// Phone validation statistics endpoint
app.get('/api/phone-validation-stats', (req, res) => {
try {
const stats = enhancedPhoneValidator.getValidationStats()
res.json({
statistics: stats,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('Statistics retrieval error:', error)
res.status(500).json({ error: 'Statistics retrieval failed' })
}
})
console.log('Enhanced Phone Validation with Carrier Intelligence initialized')Fraud Detection Integration
Integrating fraud detection with carrier intelligence provides comprehensive security for phone-based transactions.
Risk Assessment Framework
Velocity Analysis
Velocity analysis measures the frequency of actions from a specific phone number.
- Why it matters: A high volume of requests (e.g., 50 SMS requests in 5 minutes) usually indicates a bot attempt to bypass authentication or a distributed denial-of-service (DDoS) attack on your SMS gateway.
- Key threshold: Alerts should trigger when requests exceed 10 per minute or 100 per hour.
Pattern Recognition
This involves analyzing number sequences and behavioral metadata.
- Why it matters: Fraudsters often purchase blocks of sequential numbers. If your system detects a sudden influx of users with numbers ending in 0001, 0002, 0003, it’s a strong indicator of a coordinated fake account registration campaign.
- Strategy: Compare the "new user" numbers against known fraud number ranges typically associated with virtual providers.
Blacklist Management
Maintaining a dynamic list of compromised or malicious numbers.
- Why it matters: Once a number is identified in a fraud scheme, it should be blocked globally within your system.
- Process: Real-time integration with databases like Spamhaus or internal historical logs ensures that known bad actors are blocked before they even reach your processing logic.
Implementation Example
// Fraud detection integration with carrier intelligence
class FraudDetectionService {
private riskThresholds = {
velocity: 10, // requests per minute
pattern: 0.8, // pattern similarity score
blacklist: true, // any blacklist match
roaming: 0.7 // roaming risk score
}
async assessFraudRisk(phoneNumber: string, context: TransactionContext): Promise<FraudRiskAssessment> {
const [carrierInfo, velocityData, patternAnalysis] = await Promise.all([
this.getCarrierIntelligence(phoneNumber),
this.analyzeVelocity(phoneNumber, context),
this.analyzePattern(phoneNumber)
])
const riskFactors = []
let riskScore = 0
// Velocity check
if (velocityData.requestsPerMinute > this.riskThresholds.velocity) {
riskFactors.push('High velocity detected')
riskScore += 30
}
// Pattern analysis
if (patternAnalysis.similarityScore > this.riskThresholds.pattern) {
riskFactors.push('Suspicious pattern detected')
riskScore += 25
}
// Blacklist check
if (await this.isBlacklisted(phoneNumber)) {
riskFactors.push('Number is blacklisted')
riskScore += 50
}
// Roaming analysis
if (carrierInfo.isRoaming && carrierInfo.roamingRiskScore > this.riskThresholds.roaming) {
riskFactors.push('High-risk roaming detected')
riskScore += 20
}
return {
phoneNumber,
riskScore: Math.min(riskScore, 100),
riskLevel: this.calculateRiskLevel(riskScore),
riskFactors,
recommendation: this.generateRecommendation(riskScore, riskFactors),
requiresReview: riskScore > 70,
blocked: riskScore > 90
}
}
}Performance Optimization Strategies
Optimizing carrier intelligence systems requires careful attention to caching, rate limiting, and resource management to ensure that security checks don't become a bottleneck for legitimate users.
Caching Strategies
Multi-Level Caching
- L1 (Redis): Short-term in-memory cache (1-5 mins) to handle immediate retries or "burst" logins from the same device.
- L2 (Database): Long-term cache (1-24 hours) for number characteristics that rarely change frequently, like carrier name or technology support.
- L3 (CDN): Regional caching to reduce latency for global applications.
Cache Invalidation
- Time-based: Automatic TTLs (Time To Live) ensure data doesn't become stale.
- Event-driven: Trigger a refresh if a user reports a change in their service or if a porting event is detected.
Rate Limiting
Effective rate limiting protects both your budget and your service availability.
- Provider Limits: External HLR providers often charge per request and have strict caps. Implement internal queues to ensure you never exceed these limits.
- User-Based Limits: Limit how many times a single IP or User ID can trigger a carrier lookup to prevent reconnaissance by bad actors.
Implementation
class RateLimiter {
private limits = new Map<string, { count: number; resetTime: number }>()
async checkLimit(provider: string, limit: number): Promise<boolean> {
const now = Date.now()
const current = this.limits.get(provider) || { count: 0, resetTime: now + 60000 }
if (now > current.resetTime) {
current.count = 0
current.resetTime = now + 60000
}
if (current.count >= limit) {
return false
}
current.count++
this.limits.set(provider, current)
return true
}
}Implementation Best Practices
Architecture Design
Microservices Approach
Decouple carrier intelligence from your core billing or auth logic. This allows you to scale the HLR lookup service independently when traffic spikes and ensures that a failure in the lookup provider doesn't take down your entire application.
Error Handling
- Graceful degradation: If the HLR provider is down, fallback to basic regex validation and flag the number for later verification rather than blocking the user.
- Exponential backoff: Use retries that increase in delay to avoid overwhelming third-party APIs during recovery.
Security Considerations
Data Protection
Mobile network data is sensitive. Ensure that IMSI and location data are encrypted. Never store this information in plain text or in logs that are accessible to the entire development team.
API Security
Secure your intelligence endpoints with OAuth2 and implement IP whitelisting for your server-to-server communications.
Monitoring and Alerting
Don't wait for users to complain about slow logins. Monitor:
- P99 Latency: If lookups take >3 seconds, it will negatively impact sign-up conversion rates.
- Cache Hit Ratio: If your hit ratio drops below 70%, your caching strategy may be too aggressive or your TTLs too short.
Conclusion
Carrier intelligence systems provide essential capabilities for modern applications requiring phone number validation and fraud detection. Success depends on:
- Robust architecture with proper error handling and caching to maintain high availability.
- Comprehensive fraud detection with multiple risk assessment layers that go beyond simple blacklists.
- Performance optimization through intelligent caching and rate limiting to ensure a seamless user experience.
- Security-first approach with strict data protection and access controls to comply with global privacy standards.
Implement carrier intelligence with our professional solutions, designed for enterprise-scale performance and reliability.