Caller ID Spoofing Detection

Caller ID spoofing allows malicious actors to disguise their identity by displaying a false phone number. Detecting spoofed calls requires combining multiple verification layers including STIR/SHAKEN attestation, phone intelligence, behavioral analysis, and real-time reputation data to identify when caller ID doesn't match the true call origin.

Key Takeaways

  • Spoofing detection requires multiple verification signals - no single check is sufficient
  • STIR/SHAKEN attestation provides cryptographic proof of caller ID authenticity
  • Phone intelligence reveals mismatches between displayed number and actual call characteristics
  • Behavioral patterns expose spoofing even when individual calls appear legitimate

Understanding Caller ID Spoofing

Caller ID spoofing is the practice of causing the telephone network to display a phone number different from the actual originating number. While spoofing has legitimate uses (businesses displaying a main callback number, doctors calling from personal phones), it's heavily abused by scammers, robocallers, and fraudsters.

How Spoofing Works

Traditional telephone networks were built on trust - the calling party provides their caller ID, and the network transmits it without verification. This design flaw enables spoofing through several methods:

  • VoIP manipulation - SIP headers can specify any desired caller ID
  • PRI/T1 configuration - Business phone systems can set outbound caller ID
  • Spoofing services - Commercial services provide spoofing as a feature
  • SS7 exploitation - Direct manipulation of signaling protocols

Spoofing Attack Types

Attack Type Description Goal
Neighbor Spoofing Display a local number matching victim's area code Increase answer rate
Business Impersonation Spoof a bank, IRS, or company number Build false trust
Victim Impersonation Spoof the victim's own number Bypass call screening
Random Spoofing Use any number to mask identity Avoid traceback
Sequential Spoofing Rotate through number ranges Evade blocklists

STIR/SHAKEN: Cryptographic Caller ID Verification

STIR/SHAKEN (Secure Telephone Identity Revisited / Signature-based Handling of Asserted information using toKENs) is an industry framework that uses digital certificates to cryptographically sign and verify caller ID.

How STIR/SHAKEN Works

  1. Signing (STIR) - The originating carrier creates a digital signature attesting to the caller ID
  2. Transport (SHAKEN) - The signature is transmitted with the call through the network
  3. Verification - The terminating carrier validates the signature using the originating carrier's certificate
  4. Attestation Display - Results are passed to the end device/application

Attestation Levels Explained

Level Name Verification Spoofing Risk
A Full Attestation Carrier authenticated caller AND verified right to use number Low
B Partial Attestation Carrier authenticated caller but cannot verify number rights Medium
C Gateway Attestation Carrier is entry point; cannot verify originator Higher
None No Attestation No STIR/SHAKEN information provided Highest
def verify_stir_shaken(call_data):
    """Verify STIR/SHAKEN attestation and return spoofing risk."""

    stir_shaken = call_data.get('stir_shaken', {})
    attestation = stir_shaken.get('attestation')
    verified = stir_shaken.get('verified', False)
    certificate_valid = stir_shaken.get('certificate_valid', False)

    # Build verification result
    result = {
        'attestation_level': attestation,
        'signature_verified': verified,
        'certificate_valid': certificate_valid,
        'spoofing_indicators': []
    }

    # Evaluate spoofing risk
    if attestation is None:
        result['spoofing_risk'] = 'high'
        result['spoofing_indicators'].append('No STIR/SHAKEN attestation')
    elif attestation == 'C':
        result['spoofing_risk'] = 'elevated'
        result['spoofing_indicators'].append('Gateway attestation only')
    elif attestation == 'B':
        if not verified:
            result['spoofing_risk'] = 'medium'
            result['spoofing_indicators'].append('Partial attestation, verification failed')
        else:
            result['spoofing_risk'] = 'low'
    elif attestation == 'A':
        if verified and certificate_valid:
            result['spoofing_risk'] = 'very_low'
        else:
            result['spoofing_risk'] = 'medium'
            result['spoofing_indicators'].append('Full attestation but verification issue')

    return result

STIR/SHAKEN Limitations

While powerful, STIR/SHAKEN has limitations to understand:

  • Legitimate spoofers can get "A" attestation - A bad actor with a legitimate VoIP account can still get full attestation for numbers they control
  • Coverage gaps - Not all carriers fully implement STIR/SHAKEN; international calls often lack attestation
  • Gateway issues - Calls transiting through gateways lose attestation fidelity
  • Does not verify intent - A call can be fully attested and still be spam/fraud

Detect spoofed caller IDs in real-time. VeriRoute Intel provides spam scores, carrier data, and reputation signals.

Get Free API Key

Phone Intelligence for Spoofing Detection

Beyond STIR/SHAKEN, phone number intelligence provides additional spoofing detection signals by revealing mismatches between the displayed number and actual call characteristics.

Line Type Verification

If the displayed caller ID is a landline but the call characteristics indicate VoIP origin, spoofing is likely:

def detect_line_type_mismatch(caller_number, call_metadata):
    """Detect spoofing through line type mismatch."""

    # Look up the displayed caller ID
    phone_data = lookup_phone_intelligence(caller_number)
    displayed_line_type = phone_data.get('line_type')

    # Analyze call metadata for actual origin
    actual_indicators = analyze_call_origin(call_metadata)

    mismatches = []

    # Check for VoIP characteristics on non-VoIP number
    if displayed_line_type == 'landline':
        if actual_indicators.get('codec') in ['G.729', 'OPUS']:
            mismatches.append('VoIP codec detected for landline number')
        if actual_indicators.get('rtt_jitter') > 50:
            mismatches.append('Network jitter indicates VoIP origin')

    # Check for mobile characteristics
    if displayed_line_type == 'mobile':
        if not actual_indicators.get('handoff_detected'):
            mismatches.append('No cellular handoff for mobile number')

    return {
        'line_type_match': len(mismatches) == 0,
        'displayed_type': displayed_line_type,
        'mismatches': mismatches
    }

Carrier Consistency Check

Verify that the originating carrier matches the expected carrier for the displayed number:

  • LRN lookup - Identify which carrier currently owns the displayed number
  • Compare to actual origin - Check if call originated from that carrier's network
  • Flag mismatches - Carrier mismatch is a strong spoofing indicator

Geographic Consistency

Analyze whether the caller's location matches the number's geography:

  • NPA-NXX analysis - Number's assigned geography
  • IP geolocation - For VoIP calls, originating IP location
  • Network routing - Where did the call enter the network?
def check_geographic_consistency(caller_number, call_origin):
    """Detect geographic inconsistencies suggesting spoofing."""

    # Parse the displayed number's geography
    npa = caller_number[1:4]  # Area code
    number_location = lookup_npa_geography(npa)

    # Get call origin information
    origin_location = call_origin.get('geo')
    origin_ip = call_origin.get('source_ip')

    inconsistencies = []

    # Check IP geolocation vs number geography
    if origin_ip:
        ip_location = geolocate_ip(origin_ip)
        distance = calculate_distance(number_location, ip_location)

        if distance > 1000:  # More than 1000 miles apart
            inconsistencies.append({
                'type': 'ip_geography_mismatch',
                'number_location': number_location,
                'ip_location': ip_location,
                'distance_miles': distance
            })

    # Check for international origin with domestic number
    if call_origin.get('international_entry'):
        inconsistencies.append({
            'type': 'international_origin',
            'entry_country': call_origin.get('entry_country')
        })

    return {
        'geographically_consistent': len(inconsistencies) == 0,
        'inconsistencies': inconsistencies
    }

Behavioral Spoofing Detection

Analyzing patterns across multiple calls reveals spoofing campaigns that might not be detectable from a single call.

Call Pattern Analysis

Pattern Legitimate Use Spoofing Indicator
Same number, many locations Mobile user traveling Simultaneous calls from different locations
Sequential number usage Rare Calls from +1555000001, 002, 003...
High call velocity Call center operations 100+ calls/hour from single number
Number reuse Consistent business number Dormant number suddenly high activity

Impossible Travel Detection

Track the apparent location of calls from the same number:

def detect_impossible_travel(caller_number, call_history):
    """Detect physically impossible movement patterns."""

    MAX_TRAVEL_SPEED_MPH = 600  # Faster than commercial flight

    # Sort calls by timestamp
    sorted_calls = sorted(call_history, key=lambda x: x['timestamp'])

    violations = []

    for i in range(1, len(sorted_calls)):
        prev_call = sorted_calls[i-1]
        curr_call = sorted_calls[i]

        # Calculate time difference in hours
        time_diff_hours = (curr_call['timestamp'] - prev_call['timestamp']).total_seconds() / 3600

        if time_diff_hours > 0:
            # Calculate distance between call origins
            distance_miles = calculate_distance(
                prev_call['origin_location'],
                curr_call['origin_location']
            )

            required_speed = distance_miles / time_diff_hours

            if required_speed > MAX_TRAVEL_SPEED_MPH:
                violations.append({
                    'from_location': prev_call['origin_location'],
                    'to_location': curr_call['origin_location'],
                    'time_hours': time_diff_hours,
                    'distance_miles': distance_miles,
                    'required_speed_mph': required_speed
                })

    return {
        'impossible_travel_detected': len(violations) > 0,
        'violations': violations
    }

Spoofing Campaign Detection

Identify coordinated spoofing campaigns targeting your organization:

  • Number rotation patterns - Sequential or patterned number usage
  • Common origin clusters - Multiple spoofed numbers from same infrastructure
  • Timing correlations - Calls that follow predictable schedules
  • Target patterns - Systematic targeting of your number ranges

CNAM Verification

CNAM (Caller Name) data provides another verification layer for spoofing detection.

CNAM Anomalies

  • Missing CNAM - Legitimate established numbers usually have CNAM; absence is suspicious
  • Generic CNAM - Values like "WIRELESS CALLER" or "UNKNOWN" on calls claiming to be from specific organizations
  • CNAM mismatch - Caller claims to be "ABC Bank" but CNAM shows different entity
  • Recently changed CNAM - CNAM changed right before a suspicious call campaign
def verify_cnam_consistency(caller_number, claimed_identity):
    """Check if CNAM supports the caller's claimed identity."""

    phone_data = lookup_phone_intelligence(caller_number)
    cnam = phone_data.get('cnam', {})

    result = {
        'cnam_name': cnam.get('name'),
        'cnam_type': cnam.get('type'),
        'claimed_identity': claimed_identity,
        'verification_signals': []
    }

    # Check if CNAM exists
    if not cnam.get('name'):
        result['verification_signals'].append({
            'signal': 'no_cnam',
            'severity': 'medium',
            'detail': 'No CNAM record found'
        })

    # Check CNAM type vs claimed type
    if claimed_identity.get('type') == 'business':
        if cnam.get('type') == 'person':
            result['verification_signals'].append({
                'signal': 'type_mismatch',
                'severity': 'high',
                'detail': 'Claims business but CNAM shows person'
            })

    # Check name similarity
    if cnam.get('name') and claimed_identity.get('name'):
        similarity = calculate_name_similarity(
            cnam.get('name'),
            claimed_identity.get('name')
        )
        if similarity < 0.3:
            result['verification_signals'].append({
                'signal': 'name_mismatch',
                'severity': 'high',
                'detail': f'CNAM name does not match claimed identity (similarity: {similarity:.2f})'
            })

    result['likely_spoofed'] = len(result['verification_signals']) >= 2

    return result

Implementing a Spoofing Detection System

A comprehensive spoofing detection system combines all available signals:

class SpoofingDetector:
    """Multi-layer caller ID spoofing detection."""

    def __init__(self, phone_api_key):
        self.phone_api = PhoneIntelligenceAPI(phone_api_key)
        self.history_store = CallHistoryStore()

    def analyze_call(self, call_data):
        """Complete spoofing analysis for an incoming call."""

        caller_number = call_data['caller_number']

        # Gather all detection signals
        signals = {
            'stir_shaken': self._check_stir_shaken(call_data),
            'phone_intelligence': self._check_phone_intelligence(caller_number),
            'geographic': self._check_geographic_consistency(caller_number, call_data),
            'behavioral': self._check_behavioral_patterns(caller_number),
            'cnam': self._check_cnam(caller_number, call_data.get('claimed_identity'))
        }

        # Calculate composite spoofing score
        spoofing_score = self._calculate_spoofing_score(signals)

        # Determine action
        if spoofing_score >= 80:
            action = 'block'
            confidence = 'high'
        elif spoofing_score >= 60:
            action = 'challenge'
            confidence = 'medium'
        elif spoofing_score >= 40:
            action = 'flag'
            confidence = 'low'
        else:
            action = 'allow'
            confidence = 'none'

        # Store for behavioral analysis
        self.history_store.record_call(caller_number, call_data, signals)

        return {
            'caller_number': caller_number,
            'spoofing_score': spoofing_score,
            'likely_spoofed': spoofing_score >= 60,
            'confidence': confidence,
            'action': action,
            'signals': signals
        }

    def _calculate_spoofing_score(self, signals):
        """Weighted scoring of spoofing signals."""

        weights = {
            'stir_shaken': 0.30,
            'phone_intelligence': 0.25,
            'geographic': 0.20,
            'behavioral': 0.15,
            'cnam': 0.10
        }

        score = 0
        for signal_type, weight in weights.items():
            signal_data = signals.get(signal_type, {})
            signal_score = signal_data.get('spoofing_score', 0)
            score += signal_score * weight

        return min(100, score)

Response Strategies for Spoofed Calls

Graduated Response Framework

Spoofing Score Response User Experience
0-39 Allow with monitoring Normal call experience
40-59 Flag and warn Display warning to recipient
60-79 Challenge caller Require DTMF or voice verification
80-100 Block silently Caller hears ringing, call never connects

Challenge Methods

  • DTMF challenge - "Press 1 to complete your call"
  • Voice challenge - "Please say your name"
  • Callback verification - "We'll call you back at this number"
  • Out-of-band verification - "Enter the code we just texted"

Best Practices

  1. Never rely on caller ID alone - Treat it as one signal among many
  2. Implement STIR/SHAKEN verification - Use attestation as a primary signal
  3. Layer detection methods - Combine technical and behavioral analysis
  4. Track patterns over time - Single-call analysis misses campaign patterns
  5. Update thresholds regularly - Spoofing techniques evolve
  6. Balance security and UX - Aggressive blocking frustrates legitimate callers
  7. Report spoofing - Contribute to industry databases and notify carriers

Frequently Asked Questions

Is caller ID spoofing illegal?

In the US, caller ID spoofing is illegal when done "with the intent to defraud, cause harm, or wrongfully obtain anything of value" under the Truth in Caller ID Act. However, spoofing for legitimate purposes (like a doctor calling from a personal phone but displaying the office number) is legal. The challenge is that enforcement is difficult, especially for international spoofing operations.

Can STIR/SHAKEN prevent all spoofing?

No, STIR/SHAKEN has limitations. A caller with legitimate access to a phone number can still get "A" attestation and make spam/scam calls. International calls often lack attestation entirely. Additionally, not all carriers have fully implemented STIR/SHAKEN. It's a powerful tool but should be combined with phone intelligence, reputation data, and behavioral analysis.

Why would someone spoof my own number to call me?

This technique, sometimes called "mirror spoofing," is used because people are more likely to answer calls from their own number out of curiosity. Some systems that filter spam calls may also whitelist the user's own number. It's a social engineering tactic to bypass normal call screening behaviors.

How do I protect my business number from being spoofed by others?

You cannot directly prevent others from spoofing your number, but you can mitigate damage: (1) Register your numbers in STIR/SHAKEN databases so carriers can validate legitimate calls from you, (2) Monitor for reports of your number being used in spam/scam calls, (3) Work with your carrier on proactive blocking, (4) Consider number reputation services that alert you to misuse.

Related Articles

← Back to Phone Fraud Detection

Detect Spoofed Caller IDs in Real-Time

Get phone intelligence, spam scores, and carrier data to power your spoofing detection. 10 free lookups monthly.