How to Do Ad Verification with Proxies in 2025

Ad verification with proxies enables advertisers to monitor their campaigns anonymously, detect fraud, and ensure ads appear correctly across different locations by routing requests through residential IPs that mimic real user behavior.

In this guide, we'll show you how to implement a robust ad verification system using proxies, covering both basic HTTP requests and advanced browser automation techniques.

What You'll Learn

  • Set up residential proxies for ad verification
  • Implement automated verification scripts with Python
  • Build concurrent verification systems for scale
  • Create browser-based verification with Selenium
  • Detect common ad fraud patterns programmatically

Step 1: Choose and Configure Your Proxy Infrastructure

Before diving into code, you need to select the right proxy type for ad verification. Residential proxies are perfect for ad verification because they provide unique, organic, and diversely geo-located IP addresses that are almost impossible for ad fraudsters to track down.

Proxy Selection Criteria

Residential vs. Datacenter Proxies:

  • Residential proxies provide anonymity and make you look like a regular internet user, preventing fraudulent sites from knowing you're using proxies
  • Residential proxies are hard to detect as they have authentic and unique IP addresses located in different geo locations

For Roundproxies setup:

# Roundproxies configuration example
PROXY_CONFIG = {
    'endpoint': 'us.roundproxies.com:17810',  # Rotating endpoint
    'username': 'your_username',
    'password': 'your_password',
    'countries': ['US', 'UK', 'DE', 'FR'],  # Target countries
    'rotation_type': 'rotating',  # or 'sticky' for session persistence
}

Authentication Methods

Most residential proxy providers support two authentication methods:

# Method 1: Username/Password Authentication
proxy_url = f"http://{username}:{password}@{proxy_host}:{proxy_port}"

# Method 2: IP Whitelist Authentication
# Configure in your proxy dashboard, then use:
proxy_url = f"http://{proxy_host}:{proxy_port}"

Step 2: Build Your Request-Based Verification System

Let's create a lightweight verification system using Python's requests library. This approach is ideal for checking ad placement, visibility, and basic fraud detection.

Basic Ad Verification Script

import requests
from urllib.parse import urlparse
import json
import time
from datetime import datetime
import random

class AdVerifier:
    def __init__(self, proxy_config):
        self.proxy_config = proxy_config
        self.session = requests.Session()
        self.results = []
        
    def get_proxy(self, country=None):
        """Get proxy URL with optional country targeting"""
        if country:
            # For Roundproxies sticky session with country
            session_id = f"session_{random.randint(1000, 9999)}"
            proxy_url = f"http://{self.proxy_config['username']}-country-{country}-session-{session_id}:{self.proxy_config['password']}@{self.proxy_config['endpoint']}"
        else:
            # Standard rotating proxy
            proxy_url = f"http://{self.proxy_config['username']}:{self.proxy_config['password']}@{self.proxy_config['endpoint']}"
        
        return {
            'http': proxy_url,
            'https': proxy_url
        }
    
    def verify_ad_placement(self, ad_url, expected_domain, country=None):
        """Verify if ad is placed on the correct domain"""
        proxies = self.get_proxy(country)
        
        try:
            response = self.session.get(
                ad_url,
                proxies=proxies,
                timeout=10,
                headers={
                    'User-Agent': self._get_random_user_agent(),
                    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                    'Accept-Language': 'en-US,en;q=0.5',
                    'Accept-Encoding': 'gzip, deflate',
                    'DNT': '1',
                    'Connection': 'keep-alive',
                    'Upgrade-Insecure-Requests': '1'
                }
            )
            
            # Check if we're on the expected domain
            actual_domain = urlparse(response.url).netloc
            is_correct_placement = expected_domain in actual_domain
            
            # Detect common fraud indicators
            fraud_indicators = self._check_fraud_indicators(response)
            
            result = {
                'timestamp': datetime.now().isoformat(),
                'ad_url': ad_url,
                'expected_domain': expected_domain,
                'actual_domain': actual_domain,
                'correct_placement': is_correct_placement,
                'status_code': response.status_code,
                'fraud_indicators': fraud_indicators,
                'proxy_location': country or 'rotating',
                'response_time': response.elapsed.total_seconds()
            }
            
            self.results.append(result)
            return result
            
        except requests.exceptions.RequestException as e:
            return {
                'error': str(e),
                'ad_url': ad_url,
                'timestamp': datetime.now().isoformat()
            }
    
    def _check_fraud_indicators(self, response):
        """Check for common ad fraud patterns"""
        indicators = []
        
        # Check for excessive redirects
        if len(response.history) > 3:
            indicators.append('excessive_redirects')
        
        # Check for suspicious headers
        suspicious_headers = ['X-Proxy-Connection', 'X-Forwarded-For']
        for header in suspicious_headers:
            if header in response.headers:
                indicators.append(f'suspicious_header_{header}')
        
        # Check response size (empty or too small might indicate hidden ads)
        if len(response.content) < 1000:
            indicators.append('suspiciously_small_response')
        
        # Check for iframe stuffing
        if response.text.count('<iframe') > 5:
            indicators.append('possible_iframe_stuffing')
        
        return indicators
    
    def _get_random_user_agent(self):
        """Rotate user agents to appear more natural"""
        user_agents = [
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        ]
        return random.choice(user_agents)

Advanced Concurrent Verification

By distributing requests across multiple IP addresses, you can collect data faster and avoid detection. Here's how to implement concurrent verification:

import concurrent.futures
from collections import defaultdict
import threading

class ConcurrentAdVerifier(AdVerifier):
    def __init__(self, proxy_config, max_workers=10):
        super().__init__(proxy_config)
        self.max_workers = max_workers
        self.lock = threading.Lock()
        self.stats = defaultdict(int)
    
    def verify_campaign(self, ad_placements, countries=None):
        """Verify multiple ad placements concurrently"""
        if countries is None:
            countries = self.proxy_config.get('countries', [None])
        
        # Create verification tasks
        tasks = []
        for placement in ad_placements:
            for country in countries:
                tasks.append((placement['ad_url'], placement['expected_domain'], country))
        
        # Execute concurrent verification
        with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            future_to_task = {
                executor.submit(self.verify_ad_placement, *task): task 
                for task in tasks
            }
            
            for future in concurrent.futures.as_completed(future_to_task):
                task = future_to_task[future]
                try:
                    result = future.result()
                    self._update_stats(result)
                except Exception as exc:
                    print(f'Task {task} generated exception: {exc}')
        
        return self.generate_report()
    
    def _update_stats(self, result):
        """Thread-safe stats update"""
        with self.lock:
            if 'error' not in result:
                self.stats['total_verified'] += 1
                if result['correct_placement']:
                    self.stats['correct_placements'] += 1
                else:
                    self.stats['incorrect_placements'] += 1
                
                # Track fraud indicators
                for indicator in result.get('fraud_indicators', []):
                    self.stats[f'fraud_{indicator}'] += 1
    
    def generate_report(self):
        """Generate verification report"""
        return {
            'summary': dict(self.stats),
            'details': self.results,
            'fraud_rate': self._calculate_fraud_rate(),
            'placement_accuracy': self._calculate_placement_accuracy()
        }
    
    def _calculate_fraud_rate(self):
        """Calculate percentage of placements with fraud indicators"""
        total = self.stats['total_verified']
        if total == 0:
            return 0
        
        fraud_count = sum(1 for r in self.results if r.get('fraud_indicators'))
        return (fraud_count / total) * 100
    
    def _calculate_placement_accuracy(self):
        """Calculate percentage of correct placements"""
        total = self.stats['total_verified']
        if total == 0:
            return 0
        
        return (self.stats['correct_placements'] / total) * 100

Step 3: Implement Browser-Based Verification with Selenium

For more sophisticated verification that requires JavaScript execution or interaction with dynamic content, use Selenium with proxies:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
import undetected_chromedriver as uc

class BrowserAdVerifier:
    def __init__(self, proxy_config):
        self.proxy_config = proxy_config
    
    def create_driver(self, proxy_string):
        """Create undetected Chrome driver with proxy"""
        options = uc.ChromeOptions()
        
        # Proxy configuration
        options.add_argument(f'--proxy-server={proxy_string}')
        
        # Stealth options
        options.add_argument('--disable-blink-features=AutomationControlled')
        options.add_experimental_option("excludeSwitches", ["enable-automation"])
        options.add_experimental_option('useAutomationExtension', False)
        
        # Performance options
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
        options.add_argument('--disable-gpu')
        
        # Create driver
        driver = uc.Chrome(options=options)
        
        # Execute stealth scripts
        driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
        
        return driver
    
    def verify_display_ad(self, ad_url, ad_selector, country=None):
        """Verify display ad visibility and properties"""
        proxy_string = self._get_proxy_string(country)
        driver = self.create_driver(proxy_string)
        
        try:
            driver.get(ad_url)
            
            # Wait for ad to load
            wait = WebDriverWait(driver, 10)
            ad_element = wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR, ad_selector))
            )
            
            # Verify ad properties
            verification_result = {
                'visible': ad_element.is_displayed(),
                'size': ad_element.size,
                'location': ad_element.location,
                'screenshot': self._capture_ad_screenshot(driver, ad_element),
                'page_source_length': len(driver.page_source),
                'javascript_errors': self._check_js_errors(driver)
            }
            
            # Check for click fraud
            click_verification = self._verify_click_behavior(driver, ad_element)
            verification_result.update(click_verification)
            
            return verification_result
            
        finally:
            driver.quit()
    
    def _capture_ad_screenshot(self, driver, element):
        """Capture screenshot of the ad for visual verification"""
        return element.screenshot_as_base64
    
    def _check_js_errors(self, driver):
        """Check browser console for JavaScript errors"""
        logs = driver.get_log('browser')
        errors = [log for log in logs if log['level'] == 'SEVERE']
        return errors
    
    def _verify_click_behavior(self, driver, ad_element):
        """Verify ad click behavior without actually clicking"""
        # Get onclick handler
        onclick = ad_element.get_attribute('onclick')
        href = ad_element.get_attribute('href')
        
        # Check for suspicious patterns
        suspicious_patterns = [
            'window.open.*_blank.*width=1.*height=1',  # Hidden popups
            'setTimeout.*click',  # Delayed clicks
            'document.createElement.*iframe'  # Dynamic iframe creation
        ]
        
        is_suspicious = any(
            pattern in str(onclick) + str(href) 
            for pattern in suspicious_patterns
        )
        
        return {
            'has_onclick': bool(onclick),
            'has_href': bool(href),
            'suspicious_click_behavior': is_suspicious
        }
    
    def _get_proxy_string(self, country=None):
        """Format proxy string for Selenium"""
        if country:
            return f"{self.proxy_config['username']}-country-{country}:{self.proxy_config['password']}@{self.proxy_config['endpoint']}"
        return f"{self.proxy_config['username']}:{self.proxy_config['password']}@{self.proxy_config['endpoint']}"

Step 4: Create an Ad Fraud Detection System

Ad fraudsters use cloaking and proxy detection to hide shady ads. Here's how to detect common fraud patterns:

import hashlib
import re
from urllib.parse import urljoin, urlparse

class AdFraudDetector:
    def __init__(self):
        self.known_fraud_domains = set()
        self.load_fraud_indicators()
    
    def load_fraud_indicators(self):
        """Load known fraud patterns and domains"""
        # In production, load from a maintained database
        self.fraud_patterns = {
            'domain_patterns': [
                r'.*\d{5,}\..*',  # Domains with long number sequences
                r'.*[a-z]{15,}\..*',  # Extremely long random strings
            ],
            'url_patterns': [
                r'.*track.*click.*',
                r'.*redirect.*chain.*',
            ],
            'content_patterns': [
                r'visibility:\s*hidden',
                r'display:\s*none.*position:\s*absolute',
                r'width:\s*[01]px.*height:\s*[01]px',  # 1x1 pixel ads
            ]
        }
    
    def analyze_ad_network(self, response_data):
        """Comprehensive ad network analysis"""
        fraud_score = 0
        fraud_reasons = []
        
        # Check domain reputation
        domain = urlparse(response_data['url']).netloc
        if self._is_suspicious_domain(domain):
            fraud_score += 30
            fraud_reasons.append('suspicious_domain_pattern')
        
        # Analyze redirect chain
        redirect_analysis = self._analyze_redirects(response_data.get('redirect_chain', []))
        fraud_score += redirect_analysis['score']
        fraud_reasons.extend(redirect_analysis['reasons'])
        
        # Check for cloaking
        if self._detect_cloaking(response_data):
            fraud_score += 40
            fraud_reasons.append('possible_cloaking')
        
        # Analyze ad content
        content_analysis = self._analyze_content(response_data.get('content', ''))
        fraud_score += content_analysis['score']
        fraud_reasons.extend(content_analysis['reasons'])
        
        return {
            'fraud_score': min(fraud_score, 100),
            'fraud_reasons': fraud_reasons,
            'risk_level': self._calculate_risk_level(fraud_score)
        }
    
    def _is_suspicious_domain(self, domain):
        """Check if domain matches suspicious patterns"""
        for pattern in self.fraud_patterns['domain_patterns']:
            if re.match(pattern, domain):
                return True
        return domain in self.known_fraud_domains
    
    def _analyze_redirects(self, redirect_chain):
        """Analyze redirect chain for fraud indicators"""
        score = 0
        reasons = []
        
        # Too many redirects
        if len(redirect_chain) > 3:
            score += 20
            reasons.append(f'excessive_redirects_{len(redirect_chain)}')
        
        # Check for suspicious redirect patterns
        for url in redirect_chain:
            for pattern in self.fraud_patterns['url_patterns']:
                if re.search(pattern, url):
                    score += 15
                    reasons.append('suspicious_redirect_pattern')
                    break
        
        return {'score': score, 'reasons': reasons}
    
    def _detect_cloaking(self, response_data):
        """Detect cloaking techniques"""
        # Check for different content served to different user agents
        if 'alternate_responses' in response_data:
            contents = [r['content'] for r in response_data['alternate_responses']]
            
            # Calculate content similarity
            if len(set(hashlib.md5(c.encode()).hexdigest() for c in contents)) > 1:
                return True
        
        return False
    
    def _analyze_content(self, content):
        """Analyze ad content for fraud patterns"""
        score = 0
        reasons = []
        
        for pattern_name, pattern in self.fraud_patterns['content_patterns'].items():
            if re.search(pattern, content, re.IGNORECASE):
                score += 25
                reasons.append(f'suspicious_content_{pattern_name}')
        
        # Check for iframe stuffing
        iframe_count = content.count('<iframe')
        if iframe_count > 3:
            score += 20
            reasons.append(f'iframe_stuffing_{iframe_count}')
        
        return {'score': score, 'reasons': reasons}
    
    def _calculate_risk_level(self, fraud_score):
        """Calculate risk level based on fraud score"""
        if fraud_score >= 70:
            return 'high'
        elif fraud_score >= 40:
            return 'medium'
        elif fraud_score >= 20:
            return 'low'
        return 'minimal'

Step 5: Build a Complete Ad Verification Pipeline

Now let's combine everything into a production-ready verification system:

import asyncio
import aiohttp
from datetime import datetime, timedelta
import json
import logging

class AdVerificationPipeline:
    def __init__(self, proxy_config, mongodb_client=None):
        self.proxy_config = proxy_config
        self.request_verifier = ConcurrentAdVerifier(proxy_config)
        self.browser_verifier = BrowserAdVerifier(proxy_config)
        self.fraud_detector = AdFraudDetector()
        self.db = mongodb_client
        
        # Configure logging
        logging.basicConfig(level=logging.INFO)
        self.logger = logging.getLogger(__name__)
    
    async def verify_campaign_async(self, campaign_id, ad_placements):
        """Asynchronous campaign verification"""
        self.logger.info(f"Starting verification for campaign {campaign_id}")
        
        # Phase 1: Quick request-based verification
        request_results = await self._async_request_verification(ad_placements)
        
        # Phase 2: Browser verification for suspicious placements
        suspicious_placements = [
            p for p, r in zip(ad_placements, request_results)
            if r.get('fraud_score', 0) > 30
        ]
        
        browser_results = []
        if suspicious_placements:
            browser_results = await self._async_browser_verification(suspicious_placements)
        
        # Phase 3: Comprehensive fraud analysis
        final_report = self._generate_final_report(
            campaign_id, request_results, browser_results
        )
        
        # Store results
        if self.db:
            await self._store_results(final_report)
        
        return final_report
    
    async def _async_request_verification(self, placements):
        """Asynchronous HTTP request verification"""
        async with aiohttp.ClientSession() as session:
            tasks = []
            for placement in placements:
                task = self._verify_placement_async(session, placement)
                tasks.append(task)
            
            results = await asyncio.gather(*tasks, return_exceptions=True)
            return results
    
    async def _verify_placement_async(self, session, placement):
        """Single placement verification"""
        proxy = self._get_async_proxy()
        
        try:
            async with session.get(
                placement['ad_url'],
                proxy=proxy,
                timeout=aiohttp.ClientTimeout(total=10),
                headers={'User-Agent': self.request_verifier._get_random_user_agent()}
            ) as response:
                content = await response.text()
                
                result = {
                    'url': str(response.url),
                    'status': response.status,
                    'content': content[:1000],  # First 1000 chars
                    'headers': dict(response.headers),
                    'redirect_chain': [str(r.url) for r in response.history]
                }
                
                # Run fraud detection
                fraud_analysis = self.fraud_detector.analyze_ad_network(result)
                result.update(fraud_analysis)
                
                return result
                
        except Exception as e:
            self.logger.error(f"Error verifying {placement['ad_url']}: {str(e)}")
            return {'error': str(e), 'url': placement['ad_url']}
    
    def _get_async_proxy(self):
        """Get proxy URL for aiohttp"""
        return f"http://{self.proxy_config['username']}:{self.proxy_config['password']}@{self.proxy_config['endpoint']}"
    
    async def _async_browser_verification(self, placements):
        """Run browser verification for suspicious placements"""
        # Browser verification should be run sequentially to avoid resource issues
        results = []
        for placement in placements:
            try:
                result = self.browser_verifier.verify_display_ad(
                    placement['ad_url'],
                    placement.get('ad_selector', '.ad-container')
                )
                results.append(result)
            except Exception as e:
                self.logger.error(f"Browser verification error: {str(e)}")
                results.append({'error': str(e)})
        
        return results
    
    def _generate_final_report(self, campaign_id, request_results, browser_results):
        """Generate comprehensive verification report"""
        total_placements = len(request_results)
        
        # Calculate metrics
        fraud_placements = sum(1 for r in request_results if r.get('fraud_score', 0) > 50)
        error_placements = sum(1 for r in request_results if 'error' in r)
        
        report = {
            'campaign_id': campaign_id,
            'timestamp': datetime.now().isoformat(),
            'summary': {
                'total_placements': total_placements,
                'verified_placements': total_placements - error_placements,
                'fraud_placements': fraud_placements,
                'fraud_rate': (fraud_placements / total_placements * 100) if total_placements > 0 else 0,
                'high_risk_placements': sum(1 for r in request_results if r.get('risk_level') == 'high'),
                'browser_verifications': len(browser_results)
            },
            'detailed_results': {
                'request_verification': request_results,
                'browser_verification': browser_results
            },
            'recommendations': self._generate_recommendations(request_results)
        }
        
        return report
    
    def _generate_recommendations(self, results):
        """Generate actionable recommendations"""
        recommendations = []
        
        # Analyze fraud patterns
        fraud_reasons = {}
        for result in results:
            for reason in result.get('fraud_reasons', []):
                fraud_reasons[reason] = fraud_reasons.get(reason, 0) + 1
        
        # Generate recommendations based on patterns
        if fraud_reasons.get('excessive_redirects', 0) > 5:
            recommendations.append({
                'severity': 'high',
                'issue': 'Multiple placements with excessive redirects detected',
                'action': 'Review redirect chains and consider blocking domains with >3 redirects'
            })
        
        if fraud_reasons.get('iframe_stuffing', 0) > 3:
            recommendations.append({
                'severity': 'high',
                'issue': 'Iframe stuffing detected in multiple placements',
                'action': 'Implement iframe detection in pre-bid filtering'
            })
        
        return recommendations
    
    async def _store_results(self, report):
        """Store verification results in database"""
        if self.db:
            collection = self.db.ad_verification_reports
            await collection.insert_one(report)
            self.logger.info(f"Stored report for campaign {report['campaign_id']}")

Next Steps

Now that you have a complete ad verification system with proxies, here are some advanced techniques to enhance your implementation:

1. Implement Machine Learning for Fraud Detection

Train models on historical fraud patterns to automatically identify new fraud techniques:

from sklearn.ensemble import RandomForestClassifier
# Train on features like redirect count, domain age, content patterns

2. Add Real-Time Monitoring

Set up webhooks and alerts for immediate fraud detection:

# Integrate with services like Slack, PagerDuty for instant alerts

3. Scale with Kubernetes

Deploy your verification system as microservices for better scalability:

# kubernetes deployment for distributed verification

4. Use Proxy Pools Efficiently

Roundproxies offers over 100 million premium proxies with geo-targeting across 190+ countries and unlimited concurrent sessions, making it ideal for large-scale verification campaigns.

Key Takeaways

  • Always use residential proxies for ad verification to avoid detection
  • Implement concurrent verification to scale your monitoring efficiently
  • Combine request and browser-based approaches for comprehensive coverage
  • Monitor fraud indicators continuously and adapt to new patterns
  • Rotate proxies intelligently to maintain anonymity while preserving session data when needed

Remember that ad fraud cost advertisers an estimated $100 billion annually, making robust verification systems essential for protecting your advertising investments.

By following this guide and implementing these technical solutions, you'll have a production-ready ad verification system that can detect fraud, verify placements, and ensure your campaigns reach real users effectively.

Marius Bernard

Marius Bernard

Marius Bernard is a Product Advisor, Technical SEO, & Brand Ambassador at Roundproxies. He was the lead author for the SEO chapter of the 2024 Web and a reviewer for the 2023 SEO chapter.