Ad Verification Proxies

See the ad a real customer sees — right city, right connection type, same session from click to landing page · checked 3 November 2026
Ad verification

Ad verification fails quietly. You open the campaign URL from the office, the creative renders, the landing page loads, the spreadsheet says "live in all markets". Then a customer in another city sends a screenshot of a competitor's ad in the slot you paid for, or a blank page, or a consent wall that never offers a way through. The check passed because it was never a check: one IP, one city, one connection type, one cookie state.

What you are actually verifying is not the creative. It is the delivery decision — and that decision is made from three signals you can only reproduce with the right exit.

Three layers decide what renders

LayerWhat you controlWhat the ad stack seesGet it wrong and...
NetworkCountry, city, ASN typeGeo of the exit, ISP vs hosting, connection typeFallback creative, no-fill, or a page that pretends the market is unsupported
DeviceViewport, UA, is_mobile, timezone, localeDevice class, OS, browser, languageMobile-only formats invisible; layout and creative variant differ
Audience stateCookies, consent choice, logged-out vs logged-inNew vs returning, consent signal, interest bucketsYou measure the first-visit page and call it the customer experience

The first layer is the one teams handle. The second is where most budgets leak: programmatic inventory is priced and allocated by metro, so a country-level check cannot see a campaign that under-delivers in one city and over-delivers in another. The third layer is the one that produces the "it works for me" argument — a fresh profile with no consent cookie sees a different page from a returning visitor, and both are legitimate.

Match the exit to the verification task

Verification taskExit neededSessionMost common mistake
Creative / copy check on your own campaignResidential, any city in the buySticky 5 minScreenshotting a desktop page for a mobile placement
Geo delivery by city or DMAResidential, one exit per citySticky 20 min per citySampling three cities and generalising to twenty
Landing page + redirect chain integrityResidential, same city as the adSticky for the full chainRotating between click and landing page
Consent banner and CMP behaviourResidential, EU marketThree passes: accept / reject / undecidedOnly ever testing the accept path
Publisher placement and adjacencyResidential or datacenter, high volumeRotatingJudging placement from one impression
Mobile app-install and carrier formatsMobile ASNSticky 10 minUsing a fixed-line residential exit and reporting a false negative

Note the pattern: the more the task depends on being treated as a person, the more it needs a residential exit and a stable session. Only the first row is safe on datacenter, and even there the platform may serve a stripped page.

Keep one exit for the whole chain

An ad check is a sequence, not a request. Search or feed → impression → click → redirect → landing page → CTA. Frequency capping, A/B assignment and geo-redirects are all session-scoped. Rotate in the middle and every downstream measurement is taken against a different user than the one who saw the ad. In practice that produces the two most confusing reports in the discipline: "creative matched but landing page 404" and "ad served in a city we did not target".

# One verification pass = one sticky exit for 20 minutes
# Gateway form: country-city-session-ttl
PROXY = "http://user-country-de-city-berlin-session-8f21a:pass@gate.iplight.net:8000"
# session id stays fixed; ttl keeps the same exit alive across requests

# Playwright: drive the real chain, capture what rendered
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    br = p.chromium.launch(proxy={"server": "http://gate.iplight.net:8000",
                                 "username": "user-country-de-city-berlin-session-8f21a",
                                 "password": PASS})
    ctx = br.new_context(locale="de-DE", timezone_id="Europe/Berlin",
                         viewport={"width": 390, "height": 844},
                         is_mobile=True)
    pg = ctx.new_page()
    pg.goto(CAMPAIGN_URL, wait_until="networkidle", timeout=45000)

    # 1. did we leave the domain (redirect chain changed the market)?
    print("final", pg.url)
    # 2. does the geo layer agree with the exit?
    print("geo", pg.evaluate("fetch('/cdn-cgi/trace').then(r=>r.text())"))
    # 3. evidence, one per city per day
    pg.screenshot(path="de-berlin-2026-11-03.png", full_page=True)
    br.close()

Two details in that snippet do more work than the proxy itself: locale/timezone_id matching the exit, and is_mobile. A German exit with an English locale and a desktop viewport gets a different layout and often a different creative, and you will chase a "missing ad" that was a viewport mismatch.

Measure the things that can actually be wrong

MetricHow to compute itHealthy
Geo match ratePasses where the page's own geo == the exit's city ÷ total passes> 98%
Creative match ratePasses showing the expected creative variant ÷ total> 95%
Chain integrityPasses reaching the intended final URL ÷ passes that clicked100%
Landing page statusShare of passes returning 200 at the final URL> 99%
Consent variants coveredDistinct banner outcomes seen ÷ 33 of 3
Cost per verified placementMonthly proxy + storage spend ÷ placements verifiedReport, do not chase

Geo mismatch is the metric that pays for the programme. If the exit says Berlin and the page's own geo layer says Amsterdam, your targeting is leaking spend into a market you did not buy — and that is a budget conversation, not a technical one. Track it per city, weekly, and put the number in the same deck as delivery.

What a programme costs

ItemAssumptionValue
PlacementsOwn campaigns + partner placements200
CitiesTop metros in the buy8
Passes per monthTwice weekly2
Page loads200 × 8 × 23,200
Traffic per loadImages and fonts blocked≈ 700 KB
Monthly traffic3,200 × 700 KB≈ 2.2 GB
Monthly cost at $1/GBTraffic × unit price≈ $2.20

The arithmetic: 200 placements × 8 cities × 2 passes a month = 3,200 page loads, at an average of 700 KB with images blocked, is about 2.2 GB a month, so roughly $2.20 at $1/GB. Even at 10x that volume the proxy line stays smaller than the screenshot storage and the engineer's time. Treat traffic cost as noise and spend the effort on coverage: more cities, more device profiles, more consent states.

Six failure modes worth pre-empting

Frequently asked questions

Why does ad verification need residential proxies rather than datacenter ones?

Because the ad stack treats datacenter traffic differently. Google and Meta both suppress or de-prioritise ad delivery to known hosting ASNs, and many publishers' consent and geo layers serve a fallback page instead of the real one. A datacenter exit therefore does not show you the ad a customer sees — it shows you the page the system serves when it does not believe you are a customer. Residential exits come from consumer ISPs, so you land in the same delivery buckets as the audience you are checking.

How granular does the location need to be?

City, not country. Most programmatic buys are planned at DMA or city level and priced on local inventory, so a national check reports "live" for a campaign that never serves in the two cities you actually bought. Run one exit per city in the plan, at minimum the top five by spend. Where the buy is postcode- or radius-targeted, add one pin per postcode cluster.

Should the session rotate or stay sticky?

Sticky for the whole verification pass. A single check is a chain: search or feed, then the ad, then a click, then two or three redirects, then the landing page. If the exit changes mid-chain, frequency caps reset, A/B buckets change and the redirect can resolve to a different geo — you end up measuring three different users and calling it one. Keep one exit for 10–30 minutes, then move on.

Do mobile creatives need a mobile connection?

Yes. Carrier-only formats — app-install ads, in-app interstitials, carrier billing offers — are targeted at mobile networks and often will not serve to a fixed-line residential IP at all. If the plan includes mobile placements, verify them from mobile ASNs; a residential ISP exit will report a false negative and you will escalate a problem that does not exist.

How much traffic does a monthly verification programme use?

Less than most teams assume. A text-and-image page pass is roughly 300–600 KB; a video or heavy retail page is several megabytes. Two hundred placements across eight cities, once a week, lands in the 5–15 GB a month range for image-heavy checks and well under 3 GB if you block images and fonts. Screenshot storage costs more than the proxy traffic.

Can I verify ads without breaching the platform's terms?

Checking how your own campaigns render is standard brand-protection work and is what the ad platforms' own transparency tools are for. What is not acceptable: clicking your competitors' ads to burn their budget, generating fake impressions or conversions, or scraping logged-in user data. Keep verification read-only, rate-limited, and never click a paid ad you are not prepared to pay for.

Run one city before you run twenty

Residential exits from $1/GB billed by traffic, city-level targeting, sticky sessions up to 30 minutes. Verify a single market end to end before you build the matrix.

Create account