Why Static Blocklists Fall Short
Traditional fraud prevention relied heavily on static blocklists - databases of known fraudulent phone numbers, IP addresses, or device fingerprints. While blocklists still have value, they suffer from fundamental limitations in today's rapidly evolving fraud landscape.
The Blocklist Problem
- Reactive, not proactive - Numbers only get blocklisted after fraud occurs
- Easy to circumvent - Fraudsters simply obtain new numbers
- False positives - Legitimate numbers sometimes end up on lists incorrectly
- No context - A number is either blocked or allowed, with no nuance
- Stale data - Numbers change hands; yesterday's fraudster is today's legitimate user
Consider this scenario: A fraudster obtains 1,000 new VoIP numbers today. With static blocklists, every one of those numbers is "clean" until fraud is committed and reported. Dynamic risk scoring, by contrast, would immediately flag the suspicious characteristics of those numbers - recent activation, VoIP line type, high-risk carrier - even before any fraud attempt.
Dynamic Risk Scoring Fundamentals
Dynamic risk scoring evaluates multiple signals in real-time to produce a probability score indicating how likely a phone number is associated with fraud. Rather than a binary block/allow decision, you receive a nuanced score that enables tiered responses.
Core Signals for Phone-Based Risk Scoring
| Signal Category | Data Points | Why It Matters |
|---|---|---|
| Line Type | Mobile, landline, VoIP, toll-free | VoIP numbers have 3-5x higher fraud rates |
| Carrier Reputation | Carrier name, OCN, fraud history | Some carriers attract disproportionate fraud |
| Number Age | LRN activation date, days since porting | New/recently ported numbers are higher risk |
| Geographic Consistency | Area code vs. claimed location | Mismatches indicate potential spoofing |
| Spam Reports | Community complaints, FTC reports | Direct fraud/spam indicators |
| Velocity | Attempts per hour/day, unique users | Abnormal patterns signal automated attacks |
Calculating a Composite Risk Score
Effective risk scoring combines multiple signals with appropriate weighting. Here's a simplified example of how weights might be assigned:
# Example risk scoring model
def calculate_risk_score(phone_data):
score = 0
# Line type scoring (0-25 points)
line_type_scores = {
'landline': 0,
'mobile': 5,
'voip': 20,
'toll_free': 15
}
score += line_type_scores.get(phone_data['line_type'], 10)
# Number age scoring (0-25 points)
days_active = phone_data.get('days_since_activation', 365)
if days_active < 7:
score += 25
elif days_active < 30:
score += 15
elif days_active < 90:
score += 5
# Spam reputation scoring (0-30 points)
spam_score = phone_data.get('spam_score', 0)
score += min(spam_score * 0.3, 30)
# Carrier risk scoring (0-20 points)
if phone_data.get('carrier_risk') == 'high':
score += 20
elif phone_data.get('carrier_risk') == 'medium':
score += 10
return min(score, 100) # Cap at 100
Real-Time Behavioral Signals
Beyond static phone intelligence, dynamic scoring incorporates real-time behavioral signals that reveal fraud patterns as they happen.
Velocity Analysis
Velocity checks measure the frequency of actions over time windows. Fraudsters often operate at scale, creating patterns that legitimate users don't exhibit:
- Account creation velocity - How many accounts were created with this number in the last hour/day?
- Login attempt velocity - Rapid failed logins from the same number
- Transaction velocity - Multiple purchases in short timeframes
- Verification request velocity - Repeated OTP requests
# Velocity check example
def check_velocity(phone_number, action_type, window_minutes=60):
key = f"velocity:{action_type}:{phone_number}"
current_count = redis_client.get(key) or 0
thresholds = {
'account_creation': 3,
'login_attempt': 10,
'otp_request': 5,
'transaction': 5
}
threshold = thresholds.get(action_type, 5)
risk_multiplier = min(current_count / threshold, 3.0)
# Increment counter
redis_client.incr(key)
redis_client.expire(key, window_minutes * 60)
return risk_multiplier # 0-3x multiplier for risk score
Session Behavior Patterns
How a user interacts with your application provides additional risk signals:
- Time on page - Bots often complete forms impossibly fast
- Navigation patterns - Direct URL access vs. natural browsing
- Form completion - Copy/paste patterns, field timing
- Device consistency - Same number from multiple devices
Implementing Tiered Risk Responses
The power of risk scoring lies in enabling proportional responses. Rather than blocking all suspicious activity, you can increase friction appropriately.
Example Tiered Response System
| Risk Score | Risk Level | Recommended Action |
|---|---|---|
| 0-20 | Low | Proceed normally, minimal verification |
| 21-40 | Medium-Low | Standard SMS verification |
| 41-60 | Medium | Enhanced verification (voice call, document upload) |
| 61-80 | High | Manual review required, temporary hold |
| 81-100 | Critical | Block transaction, security team alert |
def determine_action(risk_score, context):
"""Determine appropriate action based on risk score and context."""
if risk_score <= 20:
return {'action': 'allow', 'verification': 'none'}
elif risk_score <= 40:
return {'action': 'allow', 'verification': 'sms_otp'}
elif risk_score <= 60:
# Medium risk - context matters
if context.get('transaction_value', 0) > 500:
return {'action': 'review', 'verification': 'voice_call'}
return {'action': 'allow', 'verification': 'sms_otp'}
elif risk_score <= 80:
return {
'action': 'hold',
'verification': 'manual_review',
'alert': True
}
else:
return {
'action': 'block',
'reason': 'high_risk_score',
'alert': True,
'log_for_analysis': True
}
Machine Learning Enhancement
While rule-based scoring provides a solid foundation, machine learning models can identify complex patterns that humans might miss.
Training Data Considerations
- Labeled fraud cases - Confirmed fraud incidents with full signal data
- Legitimate transactions - Verified good users for negative examples
- Feature engineering - Transform raw signals into predictive features
- Class imbalance - Fraud is rare; handle with SMOTE or class weights
Model Architecture Options
- Gradient Boosting (XGBoost, LightGBM) - Excellent for tabular data, handles missing values
- Random Forest - Good interpretability, resistant to overfitting
- Neural Networks - Best for complex patterns, requires more data
- Ensemble Methods - Combine multiple models for robustness
# Example ML scoring integration
import joblib
from sklearn.preprocessing import StandardScaler
class MLRiskScorer:
def __init__(self, model_path):
self.model = joblib.load(model_path)
self.scaler = joblib.load(model_path.replace('.pkl', '_scaler.pkl'))
def score(self, phone_features):
"""
phone_features: dict with keys like:
- line_type_encoded
- days_since_activation
- spam_score
- carrier_risk_encoded
- velocity_1h
- velocity_24h
- geographic_match
"""
feature_vector = self.scaler.transform([list(phone_features.values())])
probability = self.model.predict_proba(feature_vector)[0][1]
return int(probability * 100) # Convert to 0-100 score
Get real-time phone intelligence for your risk scoring. Spam scores, line type, carrier data, and more.
Get Free API KeyIntegrating Phone Intelligence APIs
VeriRoute Intel provides the phone intelligence signals that power effective dynamic risk scoring. Here's how to integrate our API into your scoring system:
import requests
def get_phone_risk_signals(phone_number, api_key):
"""Fetch comprehensive phone intelligence for risk scoring."""
response = requests.get(
f"https://api-service.verirouteintel.io/api/v1/lrn",
params={
'phone': phone_number,
'lrn': 'true',
'cnam': 'true',
'spam': 'true'
},
headers={'Authorization': f'Bearer {api_key}'}
)
data = response.json()
# Extract risk-relevant signals
return {
'line_type': data.get('lrn', {}).get('line_type'),
'carrier': data.get('lrn', {}).get('carrier'),
'days_since_activation': calculate_days(data.get('lrn', {}).get('activation_date')),
'spam_score': data.get('spam', {}).get('score', 0),
'spam_type': data.get('spam', {}).get('type'),
'is_robocaller': data.get('spam', {}).get('is_robocaller', False),
'cnam_available': data.get('cnam', {}).get('name') is not None,
'cnam_name': data.get('cnam', {}).get('name')
}
# Usage in risk scoring flow
def assess_phone_risk(phone_number):
signals = get_phone_risk_signals(phone_number, API_KEY)
base_score = calculate_base_risk_score(signals)
velocity_multiplier = check_velocity(phone_number, 'transaction')
final_score = min(base_score * velocity_multiplier, 100)
return {
'score': final_score,
'signals': signals,
'action': determine_action(final_score)
}
Performance Optimization
Risk scoring must be fast - users expect sub-second responses. Here are optimization strategies:
Caching Strategies
- Phone intelligence caching - Cache API responses for 24-48 hours; line type and carrier don't change frequently
- Reputation score caching - Cache spam scores for 1-4 hours
- Model prediction caching - Cache ML predictions for identical feature vectors
Async Processing
import asyncio
import aiohttp
async def parallel_risk_assessment(phone_number):
"""Fetch multiple signals in parallel for faster scoring."""
async with aiohttp.ClientSession() as session:
tasks = [
fetch_phone_intelligence(session, phone_number),
fetch_velocity_data(phone_number),
fetch_device_fingerprint(phone_number),
fetch_historical_data(phone_number)
]
results = await asyncio.gather(*tasks)
return combine_risk_signals(*results)
Monitoring and Tuning
Risk scoring systems require ongoing monitoring and adjustment to maintain effectiveness.
Key Metrics to Track
- Fraud detection rate - What percentage of confirmed fraud did we flag?
- False positive rate - How often do we incorrectly flag legitimate users?
- Score distribution - Are scores clustering abnormally?
- Threshold effectiveness - Are our action thresholds optimized?
A/B Testing Thresholds
Continuously test different threshold configurations:
def get_action_thresholds(experiment_group):
"""Return thresholds based on A/B test group."""
thresholds = {
'control': {
'low': 20, 'medium': 50, 'high': 75, 'critical': 90
},
'variant_a': {
'low': 25, 'medium': 55, 'high': 80, 'critical': 92
},
'variant_b': {
'low': 15, 'medium': 45, 'high': 70, 'critical': 88
}
}
return thresholds.get(experiment_group, thresholds['control'])
Best Practices Summary
- Layer your defenses - Use blocklists AND dynamic scoring together
- Start simple - Begin with rule-based scoring, add ML as you gather data
- Monitor continuously - Fraud patterns shift; your scoring must adapt
- Balance friction - Too aggressive scoring drives away legitimate users
- Cache intelligently - Speed matters, but staleness creates risk
- Log everything - Detailed logs enable model improvement and forensics
- Review edge cases - Regularly audit borderline scores for threshold tuning
Dynamic vs. Static: Side-by-Side Comparison
| Aspect | Static Blocklists | Dynamic Risk Scoring |
|---|---|---|
| New fraud detection | Only after incident | Predictive, before fraud occurs |
| Response type | Binary block/allow | Tiered, proportional |
| Adaptation speed | Manual updates | Real-time, automatic |
| False positives | Difficult to reverse | Self-correcting with feedback |
| Maintenance | List management overhead | Model training and tuning |
| User experience | Harsh rejections | Graduated friction |
Frequently Asked Questions
How accurate is dynamic risk scoring compared to blocklists?
Dynamic risk scoring typically catches 30-50% more fraud than blocklists alone because it can identify suspicious patterns before a number is reported. The key advantage is predictive capability - scoring can flag a brand-new VoIP number with suspicious characteristics even though it has no fraud history.
What signals are most predictive of phone fraud?
The most predictive signals are typically: (1) VoIP line type, (2) number age/activation recency, (3) existing spam reports, (4) velocity patterns across your platform, and (5) carrier reputation. Combining these signals provides significantly better prediction than any single signal alone.
How fast should risk scoring be?
Risk scoring should complete in under 200ms for inline decisions (account creation, login). For pre-transaction checks, up to 500ms is acceptable. Achieve this through intelligent caching, parallel API calls, and optimized model inference. Phone intelligence APIs like VeriRoute Intel typically respond in 50-100ms.
Should I still use blocklists if I have dynamic scoring?
Yes - a defense-in-depth approach uses both. Blocklists provide an immediate block for known-bad actors without consuming scoring resources. Dynamic scoring handles the gray area where blocklists can't help. Think of blocklists as your first line of defense and dynamic scoring as your intelligent second layer.