Back to blog

Guide

Ad Placement Verification Example: End-to-End Workflow

See an ad placement verification example that shows how to test geo-targeting, creatives, fraud signals, and reporting with location-specific checks at scale.

Walking through a concrete ad placement verification example is more useful than abstract guidance for teams building or evaluating their own verification setup. This post describes a complete verification workflow for a geo-targeted display campaign — from setup through reporting — using proxy-based verification.

The Campaign Being Verified

The campaign in this example is a display campaign targeting three metro areas: New York, London, and Paris. The campaign runs on two publisher networks: a programmatic exchange and a direct publisher placement. The advertiser needs to verify:

  1. The ad is being served in the correct markets (geographic targeting is working)
  2. The correct creative is appearing (no unauthorized creative variations)
  3. The ad clicks through to the correct landing page
  4. No brand safety violations (the ad is not appearing adjacent to excluded content categories)

Verification Setup

Proxy configuration

Each verification check uses a residential proxy IP targeting the specific city of the verification market. Geographic accuracy is verified at the start of each session before any verification data is collected.

The session configuration for this campaign:

  • Proxy type: Rotating residential
  • Targeting: City-level — New York (US), London (GB), Paris (FR)
  • Session mode: Sticky, 10-minute TTL — long enough to cover the full verification flow (ad load → click → landing page)
  • Concurrency: One session per market per verification run, three sessions running in parallel

Browser configuration

The verification uses a Playwright-based browser automation setup. Key configuration:

  • Full headless Chromium with JavaScript execution enabled
  • User-Agent set to a current Chrome desktop version
  • Viewport set to 1920×1080
  • Language and timezone set to match the proxy's target market (en-US for New York, en-GB for London, fr-FR for Paris)
  • Cookies cleared between sessions to ensure each check starts from a clean first-visit profile

The Verification Workflow (Step by Step)

Step 1: Session initialization and geo-verification

Before loading any publisher page, the browser makes a test request to a geolocation lookup service. The script checks that the returned city matches the intended target.

async def verify_geo(page, expected_city, expected_country):
    await page.goto("https://api.ipify.org?format=json")
    # Also check a geo lookup
    resp = await page.evaluate("""
        async () => {
            const r = await fetch('https://ipapi.co/json/');
            return r.json();
        }
    """)
    city_ok = expected_city.lower() in resp['city'].lower()
    country_ok = resp['country_code'] == expected_country
    return city_ok and country_ok

Sessions that fail geo-verification are discarded and a new session is started. This prevents verification data from being collected through an IP in the wrong location.

Step 2: Load the publisher page

The browser navigates to the publisher page where the ad should appear. The script waits for ad network JavaScript to load and execute — this typically requires a 3–5 second wait after initial page load for the ad auction to complete and creatives to render.

async def load_publisher_page(page, url):
    await page.goto(url, wait_until="networkidle")
    # Wait additional time for ad auction to complete
    await page.wait_for_timeout(4000)

Step 3: Detect and capture the ad placement

The script looks for the ad creative within the expected ad slot. For display ads, this typically means finding an iframe or a specific container element and capturing what is inside it.

async def capture_ad(page, slot_selector):
    slot = await page.query_selector(slot_selector)
    if not slot:
        return {"found": False, "reason": "slot_missing"}
    
    # Capture screenshot of the ad slot
    screenshot = await slot.screenshot()
    
    # Check if the slot has content (non-empty iframe or visible creative)
    is_visible = await slot.is_visible()
    bounding_box = await slot.bounding_box()
    has_area = bounding_box and bounding_box["width"] > 10 and bounding_box["height"] > 10
    
    return {
        "found": is_visible and has_area,
        "screenshot": screenshot,
        "bounding_box": bounding_box,
    }

Step 4: Verify the creative

The captured creative is compared against the approved creative for the target market. For this campaign, the New York creative uses English copy, the London creative uses English copy with UK pricing, and the Paris creative uses French copy.

Creative verification can be done at multiple levels:

Screenshot comparison: Compare the captured screenshot against a reference image for the approved creative. This catches wrong creatives, corrupted renders, and unauthorized variations.

DOM inspection: For HTML5 creatives, check that specific elements (headline text, brand logo, call-to-action button) are present in the expected format.

Creative source URL: Check the URL from which the creative assets were served — it should match the authorized creative delivery domain, not a third-party redirect chain.

Step 5: Verify click-through

The script simulates a click on the ad and verifies that the landing page URL matches the expected destination.

async def verify_clickthrough(page, expected_landing_domain):
    async with page.expect_navigation():
        await page.click('[data-ad-slot] a')
    
    final_url = page.url
    return {
        "landed": expected_landing_domain in final_url,
        "final_url": final_url,
        "redirect_chain": []  # populated from page navigation events
    }

Redirect chain verification is important for affiliate fraud detection: an approved placement that routes through unauthorized redirect chains may indicate affiliate hijacking.

Step 6: Brand safety check

The script checks the publisher page content surrounding the ad slot against the campaign's exclusion categories. This involves extracting the page's visible text and checking it against keywords or content categories in the exclusion list.

For IAB content category compliance, the page content can be passed to a classification API after collection. For keyword-based exclusions, a local check against the exclusion list is sufficient and faster.

Step 7: Fraud signal collection

While the verification session is active, the script collects additional signals relevant to ad fraud detection:

  • Ad load time from page navigation to creative render (unusually fast load times can indicate pre-rendered or cached fraud inventory)
  • Whether the ad slot was in the viewport when the creative loaded (below-the-fold placements that are never viewable are a fraud signal)
  • Whether the publisher page passed basic credibility checks (legitimate domain, real content, not a MFA/made-for-advertising template)

Reporting Structure

Each verification run produces a record per market per publisher:

{
  "campaign_id": "camp_9821",
  "market": "New York",
  "publisher": "programmatic_exchange",
  "timestamp": "2026-09-26T14:32:00Z",
  "proxy_ip": "redacted",
  "geo_verified": true,
  "ad_found": true,
  "creative_match": true,
  "clickthrough_verified": true,
  "landing_url": "https://brand.com/landing-ny",
  "brand_safety_pass": true,
  "viewability": "above_fold",
  "fraud_signals": []
}

Across a verification run covering three markets and two publishers, this produces six records. Running three times daily generates 18 records per day — a meaningful audit trail for a campaign of this scale.

What This Workflow Catches

The end-to-end verification above catches:

  • Geographic mistargeting (ad served in wrong market)
  • Wrong creative delivery (brand asset errors, unauthorized variations)
  • Landing page mismatches (broken links, redirect chains, wrong localization)
  • Brand safety violations adjacent to the placement
  • Basic fraud signals (non-viewable placements, suspicious ad load behavior)

For larger campaigns across more markets and publishers, the same structure scales by increasing the number of parallel verification sessions and adding markets to the session configuration. FlameProxies provides residential proxy access across 180+ countries with city-level targeting, the geographic foundation this verification workflow depends on.