Browser Fingerprinting Explained: Canvas, WebGL, WebRTC Leaks and What a Proxy Can’t Hide

Developer guide · Last reviewed 20 October 2026 · 17 min read

Browser fingerprinting is the practice of identifying a browser from the characteristics it exposes, such as its TLS handshake, headers, screen, fonts, GPU and network addresses, instead of from something stored on the device. It is why a new proxy IP alone rarely makes a session look new, and why one mismatch, such as a WebRTC leak of your real IP, can undo a careful setup. This guide covers each fingerprint layer, canvas and WebGL fingerprinting, WebRTC leaks and how to test for them, and exactly which signals a proxy changes and which it cannot touch.

The short version

A proxy changes where your traffic comes from: the exit IP, its ASN and its location. It does not change your browser. The TLS handshake, HTTP/2 settings, headers, JavaScript APIs (navigator, screen, timezone, fonts, canvas, WebGL, audio), WebRTC and behavior pass through untouched. Detection cares less about uniqueness than about whether your signals agree, so aim for consistency: timezone, locale and geolocation that match the exit IP, a real browser engine instead of a spoofed User-Agent, and WebRTC that cannot bypass the proxy.

Scope. This explainer is for teams running browsers through proxies for legitimate work: localized QA, ad verification, privacy hardening, or public-data collection within a site’s terms and rate limits. It explains how detection works so you avoid accidental contradictions, not how to get around security on systems you are not authorized to use.

Key takeaways

  • A fingerprint is computed, not stored. Clearing cookies or opening a private window does not change your GPU, fonts or TLS stack.
  • Fingerprints span the network, TLS, HTTP, JavaScript, WebRTC and behavior layers. A proxy only replaces the network layer.
  • Contradictions get flagged more often than uniqueness does: a German IP with a Los Angeles timezone, a Windows User-Agent with a Linux TCP stack, a desktop GPU string on a “phone”.
  • WebRTC may expose your real public IP if UDP bypasses an HTTP proxy. This depends on browser and network settings. Test for leaks, then restrict non-proxied UDP when necessary.
  • In Playwright, set locale, timezone_id and geolocation from the exit IP of a sticky session, and use the HTTP proxy URL.
  • Test in the exact configuration you run in production. Results from different test pages are not comparable scores.

What Is Browser Fingerprinting?

Identification without storage

A browser fingerprint is a set of attributes that a website can observe about your browser and device, combined into something stable across visits and distinctive across users. Your screen size and timezone are each shared by millions; twenty or thirty such attributes together can be rare.

Some attributes are read with JavaScript (fonts, the GPU renderer, how your machine draws text). Others are observed passively on the server: cipher-suite order in the TLS handshake, HTTP header order, TCP options.

Banks and payment providers use fingerprints for fraud prevention and to flag logins from unfamiliar devices. Anti-bot vendors use them to separate automated traffic from people. Ad-tech companies have used them to track users without cookies, which is the use regulators object to.

How fingerprinting differs from cookies

The core difference is where the identifier lives. A cookie is an identifier the site stores in your browser and reads back later. A fingerprint is recomputed from your browser’s characteristics on every visit, so there is nothing to delete.

AspectCookiesFingerprint
Where it livesStored on your deviceDerived from your device, held by the site
Can you delete it?Yes, clear storageNo, only change the inputs
Private windowStarts emptyMostly the same values
Visibility to youListed in DevToolsLargely invisible
StabilityUntil expiry or deletionUntil software, hardware or settings change

Sites use both: the cookie ties visits together cheaply, and the fingerprint survives when the cookie is gone.

The Signal Layers of a Browser Fingerprint

From the TCP packet to your mouse

Think of a fingerprint as a stack of layers, ordered by how early in the connection each becomes visible. The last column is the question proxy users care about.

LayerExample signalsHow it is readChanged by a proxy?
NetworkIP address, ASN, IP type (residential, mobile, datacenter), GeoIP locationServer sideYes, you get the exit’s
TCP/IP stackTTL, window size, MSS, TCP option orderServer side, passiveYes, but to the exit’s stack
TLS handshakeCipher suites, extensions, ALPN, curves (JA3, JA4)Server side, passiveNo
HTTP/2SETTINGS values, window update, pseudo-header orderServer side, passiveNo
HTTP headersUser-Agent, Accept-Language, header order, Sec-CH-UA-* Client HintsServer sideNo (over HTTPS)
navigator and screenPlatform, CPU cores, memory, touch points, resolution, pixel ratio, webdriverJavaScriptNo
Timezone and IntlIANA timezone, UTC offset, date and number formattingJavaScriptNo
FontsInstalled fonts measured by text width or document.fontsJavaScript, CSSNo
Canvas, WebGL, audioRendered pixel hashes, GPU vendor and renderer, audio outputJavaScriptNo
WebRTCICE candidates: local and public IP addressesJavaScript plus STUNNo, and it can expose the real IP
BehaviorMouse paths, scrolling, typing rhythm, navigation timingJavaScript eventsNo

Network and TCP/IP

The IP is the only part of the fingerprint the proxy owns. Sites look up its ASN (consumer ISP, mobile carrier or hosting company), its reputation and its GeoIP location; see datacenter vs residential proxies for how exit types compare.

A subtle point: the TCP connection that reaches the website is opened by the proxy exit, so passive TCP/IP fingerprinting (TTL, window size, MSS, option order) describes the exit’s operating system. Some systems compare that guess with the OS your User-Agent claims, and timing gaps between the TCP round trip and in-page measurements can hint that a relay is in the path.

TLS and HTTP/2

Before any HTTP request, your client sends a TLS ClientHello listing protocol versions, cipher suites, extensions and supported groups. Each browser build produces a characteristic ClientHello, and hashes such as JA3 and the newer JA4 family condense it into a short string. HTTP/2 adds SETTINGS values, the initial window update and pseudo-header order, which differ between engines and libraries.

So a Python client sending a Chrome User-Agent is easy to spot: the headers say Chrome, the handshake says Python. A proxy cannot fix this. With an HTTP CONNECT tunnel or SOCKS5, TLS runs end to end between your client and the website, and the proxy only forwards encrypted bytes.

HTTP headers and Client Hints

Browsers send a predictable set of headers in a predictable order. Chromium-based browsers also send low-entropy Client Hints on HTTPS by default (Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform), and sites can request high-entropy ones through Accept-CH or navigator.userAgentData.getHighEntropyValues(). Override only the User-Agent string and the hints can tell a different story; see MDN’s Client Hints guide.

JavaScript APIs

JavaScript exposes the largest surface: navigator properties (platform, hardwareConcurrency, deviceMemory, maxTouchPoints, languages, webdriver), screen and devicePixelRatio, the timezone from Intl.DateTimeFormat().resolvedOptions().timeZone, fonts, and the rendering surfaces below. Automation adds tells too: navigator.webdriver is true under Playwright, Puppeteer or Selenium, as covered in Headless Browsers 101.

Behavior

The last layer is how the session acts: pointer movement, scrolling, keystroke timing, page order, and whether assets load the way a real page load would. No network tool influences it.

Entropy and Consistency: What Actually Gets Flagged

Contradictions matter more than rarity

Fingerprinting research measures distinctiveness in bits of entropy. If one in 16 visitors shares your timezone, that attribute carries 4 bits (log2 16). Independent attributes add up, and about 33 bits single out one person among eight billion. Real attributes are correlated, so totals are lower, but the arithmetic shows why a handful of ordinary values can become unique.

How unique browsers really are depends heavily on the population measured. The EFF’s 2010 Panopticlick study found that 83.6% of browsers in its sample had a unique fingerprint. Later large-scale research on a general-audience website found far lower uniqueness, roughly a third of fingerprints, largely because mobile devices and standardized browser builds look alike.

For anti-bot and fraud systems the stronger signal is consistency: do all layers describe one plausible device in one plausible place? Real users are messy but coherent. Automation and clumsy spoofing produce contradictions:

  • The exit IP geolocates to Germany, but Intl reports America/Los_Angeles and Accept-Language is en-US only.
  • The User-Agent claims Windows, Client Hints say Linux, and the passive TCP fingerprint looks like Linux too.
  • The User-Agent claims an iPhone, but maxTouchPoints is 0 and WebGL reports a desktop GPU.
  • The WebGL renderer is a software rasterizer such as SwiftShader, which is common in headless servers and rare on consumer laptops.
  • A canvas hash changes on every page load, which tells a script the readout is being randomized.
  • A residential exit IP in Spain, with a WebRTC candidate revealing a datacenter IP in Virginia.

A unique fingerprint is normal. A fingerprint that contradicts itself is what gets flagged.

Vendors typically combine such checks with IP reputation and behavior into a risk score rather than blocking on one. Our explainer on how DataDome detects automated traffic walks through one vendor’s layered approach.

Canvas, WebGL and Audio Fingerprinting

Same instructions, slightly different pixels

Canvas fingerprinting, first described in the 2012 academic paper “Pixel Perfect”, asks the browser to draw text and shapes into an off-screen <canvas> and then reads the pixels back. The instructions are identical for everyone, but fonts, anti-aliasing, emoji sets, the graphics library, GPU and driver leave small differences. Hash the pixels and you get a value that is stable on one machine and differs across many.

The illustrative snippet below shows what test pages measure; it sends nothing anywhere.

Example 1 — how a canvas hash is derived (JavaScript)
// Illustrative only: how a canvas fingerprint is derived.
async function canvasHash() {
    const canvas = document.createElement("canvas");
    canvas.width = 280;
    canvas.height = 60;
    const ctx = canvas.getContext("2d");

    ctx.textBaseline = "top";
    ctx.font = "16px Arial";
    ctx.fillStyle = "#f60";
    ctx.fillRect(120, 1, 60, 20);
    ctx.fillStyle = "#069";
    ctx.fillText("Fingerprint demo 1.0 \u{1F600}", 2, 15);
    ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
    ctx.fillText("Fingerprint demo 1.0 \u{1F600}", 4, 17);

    // Tiny rendering differences (fonts, anti-aliasing, GPU, OS)
    // change the pixels, so they change the hash.
    const bytes = new TextEncoder().encode(canvas.toDataURL());
    const digest = await crypto.subtle.digest("SHA-256", bytes);
    return Array.from(new Uint8Array(digest))
        .map((b) => b.toString(16).padStart(2, "0"))
        .join("");
}

WebGL: the GPU in plain text

WebGL adds a rendered image, hashed like a canvas, plus metadata: extensions, limits such as maximum texture size and, through WEBGL_debug_renderer_info, the unmasked GPU vendor and renderer. The standard VENDOR and RENDERER values are usually generic; the unmasked ones show the hardware. Some browsers return a coarsened renderer string to limit this, as noted on MDN’s WEBGL_debug_renderer_info page.

Example 2 — reading WebGL parameters (JavaScript)
function webglInfo() {
    const gl = document.createElement("canvas").getContext("webgl");
    if (!gl) {
        return null;  // WebGL disabled is itself a signal
    }
    const info = {
        vendor: gl.getParameter(gl.VENDOR),      // usually generic
        renderer: gl.getParameter(gl.RENDERER),  // usually generic
        maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
        extensions: gl.getSupportedExtensions().length,
    };
    const dbg = gl.getExtension("WEBGL_debug_renderer_info");
    if (dbg) {
        info.unmaskedVendor = gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL);
        info.unmaskedRenderer = gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL);
    }
    return info;
}

When we ran it in Playwright’s default headless Chromium, the generic values came back as WebKit and WebKit WebGL, while the unmasked renderer named SwiftShader, Chromium’s software renderer, even though the test machine had a GPU. That string is rare on consumer devices, and no exit IP, in any country, changes it.

AudioContext

Audio fingerprinting renders a short signal offline through an oscillator and compressor and sums the samples. Floating-point differences in the audio stack make the result vary between engines and platforms.

Example 3 — an offline audio readout (JavaScript)
async function audioSample() {
    const ctx = new OfflineAudioContext(1, 5000, 44100);
    const osc = ctx.createOscillator();
    osc.type = "triangle";
    osc.frequency.value = 10000;
    const compressor = ctx.createDynamicsCompressor();
    osc.connect(compressor);
    compressor.connect(ctx.destination);
    osc.start(0);
    const buffer = await ctx.startRendering();
    const samples = buffer.getChannelData(0);
    let sum = 0;
    for (let i = 4500; i < samples.length; i++) {
        sum += Math.abs(samples[i]);
    }
    return sum;  // differs slightly across engines and platforms
}

How browsers push back

Browsers defend in two opposite ways. Brave randomizes canvas, WebGL and audio output per site and session, so the hash stops being stable. Tor Browser and Firefox’s privacy.resistFingerprinting aim for uniformity, so many users report the same values. Randomization can be detected, though: CreepJS, for example, flags values that contradict each other or change between reads.

WebRTC Leaks: What Leaks and Why

UDP does not go through your HTTP proxy

WebRTC lets browsers make peer-to-peer audio, video and data connections. To find a working path between two peers, the browser gathers ICE candidates, which are network addresses at which it might be reachable:

  • host candidates: the addresses of your local network interfaces, such as 192.168.1.20.
  • srflx (server reflexive) candidates: your public IP and port as seen by a STUN server on the internet.
  • relay candidates: addresses on a TURN relay server, if one is configured.

Any page can create an RTCPeerConnection, point it at a public STUN server and read the candidates, with no permission prompt. This is what a WebRTC leak test does:

Example 4 — listing ICE candidates (JavaScript)
// What a WebRTC leak test does under the hood.
async function listIceCandidates() {
    const pc = new RTCPeerConnection({
        iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
    });
    pc.createDataChannel("probe");  // forces ICE gathering
    const candidates = [];
    pc.onicecandidate = (event) => {
        if (event.candidate && event.candidate.candidate) {
            // e.g. "candidate:... udp ... 203.0.113.7 54321 typ srflx ..."
            candidates.push(event.candidate.candidate);
        }
    };
    await pc.setLocalDescription(await pc.createOffer());
    await new Promise((resolve) => setTimeout(resolve, 3000));
    pc.close();
    return candidates;
}

The leak happens because the browser’s proxy setting covers HTTP and HTTPS, while STUN is UDP, which an HTTP proxy does not carry. The STUN request leaves your network directly, the STUN server sees your real public IP, and the page reads it from the srflx candidate. The site now has two IPs for one session.

Figure 1 — how a WebRTC leak bypasses an HTTP proxy
                HTTPS via CONNECT tunnel
 Browser ------------------------------> Proxy gateway --> exit IP --> website
    |                                                      (sees exit IP)
    |
    |   UDP STUN binding request (not proxied)
    +----------------------------------------------------> STUN server
                                                           |
          srflx candidate = your real public IP  <---------+
          page JavaScript reads it and reports it

What about local IPs?

Older browsers exposed private LAN addresses in host candidates. Current Chrome and Firefox usually replace them with random mDNS names ending in .local, so the srflx public IP is the part that still matters. Check IPv6 separately: if your machine has a public IPv6 address and the proxy path is IPv4, an IPv6 candidate can identify your connection on its own.

A system-wide VPN routes UDP too, so the STUN server usually sees the VPN’s address unless IPv6 or split tunneling bypasses it. See residential proxy vs VPN.

How to Run a WebRTC Leak Test

Five minutes, in the configuration you actually use
  1. Note your real public IP with the proxy off, for example by opening https://api.ipify.org.
  2. Configure the proxy exactly as you will use it: same browser, same profile, same extension or automation launch flags.
  3. Open a WebRTC leak test page such as browserleaks.com/webrtc and wait for ICE gathering to finish.
  4. Compare. The page’s “remote IP” should be the proxy exit. No public IP in the WebRTC candidate list should be your real IPv4 or IPv6 address.
  5. Repeat after browser updates, extension changes or new automation flags.

Reading the results: a .local host candidate is normal. No candidates means WebRTC is disabled or restricted, which protects the IP but is a less common configuration. A srflx candidate matching your real IP is the leak.

Preventing WebRTC Leaks

Restrict it rather than switch it off

Chromium exposes one setting, WebRTC IP handling, through several doors, with modes from RFC 8828. The value that stops the leak is disable_non_proxied_udp: TCP on the public interface, UDP only if the proxy supports it. In our Playwright Chromium tests, default_public_interface_only still produced a srflx candidate with the machine’s public IP, so it is not enough.

MethodWhereWhat to setTrade-off
Enterprise policyChrome, ChromeOS (91+)WebRtcIPHandling = disable_non_proxied_udpManaged devices, fleet-wide
Command-line switchChromium, automation--force-webrtc-ip-handling-policy=disable_non_proxied_udpPer launch; easy in Playwright
Extension APIChrome extensionschrome.privacy.network.webRTCIPHandlingPolicyDepends on the extension
Firefox prefsabout:configmedia.peerconnection.ice.proxy_only_if_behind_proxy, or media.peerconnection.enabled = falseDisabling breaks calls
Anti-detect browserPer profileThe profile’s WebRTC modeCheck the vendor’s options

The policy is documented in the Chrome Enterprise policy list, and the extension setting in the chrome.privacy API reference. Firefox’s prefs media.peerconnection.ice.default_address_only and media.peerconnection.ice.no_host further limit which interfaces and host candidates are exposed.

Disabling WebRTC entirely ends the leak but breaks video calls and puts you in a small group of browsers with no RTCPeerConnection. Restricting UDP to the proxy removes the real IP while keeping WebRTC usable on most sites.

The ProxyEmpire Proxy Manager extension

For everyday browsing in Chrome, the free ProxyEmpire Proxy Manager extension handles the common mismatches in one place: it includes WebRTC leak protection, matches the browser’s timezone and language to the proxy location, and has a kill switch that stops traffic if the proxy drops. Run a leak test after enabling it, as you would with any extension.

Anti-detect browsers

Anti-detect browsers manage the whole fingerprint as a profile (WebRTC mode, timezone, locale, geolocation, screen, fonts) bound to one proxy, which is practical when you run many separate browser identities, such as agency ad accounts. ProxyEmpire has setup guides for the major ones, including Multilogin, AdsPower and GoLogin, collected on the integrations page.

What a Proxy Changes, and What It Can’t Hide

One layer out of many

A proxy replaces the network identity of your traffic. Everything above the network layer is produced by your client and arrives at the website unchanged.

Exit IPThe website sees the proxy’s exit address, not yours.
ASN and IP typeA residential ISP, a mobile carrier or a datacenter, depending on the pool.
GeoIP locationCountry, region or city, set by your targeting.
ReputationThe history attached to that IP, good or bad.

What a proxy cannot hide or change:

  • The TLS and HTTP/2 fingerprint. The handshake is tunneled end to end, so it is your client’s.
  • Headers and Client Hints. Over HTTPS the proxy cannot read or rewrite them.
  • Every JavaScript signal. Screen, fonts, GPU, canvas, audio, navigator, timezone and language come from the browser.
  • WebRTC. Unless restricted, it can report your real IP alongside the proxy’s.
  • Cookies, storage and logins. One account logged in from two exits links both sessions, whatever the fingerprint.
  • Behavior. Timing and interaction patterns are yours.

You can see the split for yourself with a TLS echo service. Run the same request with and without the proxy: the reported IP changes, the TLS and HTTP/2 fingerprints do not.

Example 5 — the proxy changes the IP, not the TLS fingerprint (shell)
# Quick check that the proxy works
curl -x http://USERNAME:[email protected]:5000 https://api.ipify.org

# Direct: your IP, curl's JA4 and HTTP/2 fingerprint
curl -s https://tls.peet.ws/api/all | jq '.ip, .tls.ja4, .http2.akamai_fingerprint_hash'

# Through the proxy: a different IP, the same JA4 and HTTP/2 hash
curl -s -x http://USERNAME:[email protected]:5000 \
    https://tls.peet.ws/api/all | jq '.ip, .tls.ja4, .http2.akamai_fingerprint_hash'

Our guides on anonymous proxies and how to hide your IP address cover the same split from the user’s side. The proxy supplies a clean, correctly located exit; making the browser agree with it is your job.

Matching Timezone, Locale and Geolocation to the Proxy in Playwright

Derive the profile from the exit, then launch

Playwright can emulate the three location signals that most often contradict a proxy. On browser.new_context(), locale sets navigator.language, the Accept-Language header and Intl formatting; timezone_id sets the IANA timezone; geolocation plus permissions=["geolocation"] answers the Geolocation API with your coordinates. The options are documented in Playwright’s emulation guide.

Order matters. Look up the exit IP through a sticky session, then launch the browser through the same session. If the IP rotates in between, your matched timezone belongs to a different IP. Sticky sessions are explained in sticky vs rotating proxies and in the sticky sessions docs.

Step 1: profile the exit IP

This example asks ipinfo.io’s free JSON endpoint (rate-limited without a token) about the exit. In production use your own GeoIP database or a paid lookup; providers disagree near borders and for mobile ranges. If you need a specific city, target it in ProxyEmpire and set its timezone deliberately.

Example 6 — derive timezone, locale and coordinates from the exit IP (Python)
import os
from urllib.parse import quote

import requests
from playwright.sync_api import sync_playwright

GATEWAY = "v2.proxyempire.io:5000"
# Copy a sticky-session (Same IP) username from
# Proxy Manager -> Connection Builder in the ProxyEmpire dashboard.
PROXY_USER = os.environ["PROXYEMPIRE_USER"]
PROXY_PASS = os.environ["PROXYEMPIRE_PASS"]

# Extend for the countries you target. Pick the locale real users there run.
LOCALES = {
    "US": "en-US", "GB": "en-GB", "CA": "en-CA", "DE": "de-DE",
    "FR": "fr-FR", "ES": "es-ES", "IT": "it-IT", "BR": "pt-BR",
}


def exit_profile():
    """Ask a GeoIP service what the proxy's exit IP looks like."""
    url = "http://{}:{}@{}".format(
        quote(PROXY_USER, safe=""), quote(PROXY_PASS, safe=""), GATEWAY
    )
    resp = requests.get(
        "https://ipinfo.io/json",
        proxies={"http": url, "https": url},
        timeout=30,
    )
    resp.raise_for_status()
    info = resp.json()
    lat, lon = (float(x) for x in info["loc"].split(","))
    return {
        "ip": info["ip"],
        "country": info["country"],
        "timezone": info["timezone"],
        "locale": LOCALES.get(info["country"], "en-US"),
        "geolocation": {"latitude": lat, "longitude": lon, "accuracy": 100},
    }

Step 2: launch Chromium with the matching context

Use the http:// proxy URL here. Playwright’s Chromium rejects username and password authentication on SOCKS5 proxies (“Browser does not support socks5 proxy authentication”), and the ProxyEmpire gateway supports HTTP as well as SOCKS5. The launch flag restricts WebRTC to the proxy, as described above.

Example 7 — a Playwright context that agrees with its proxy (Python)
READ_SIGNALS = """() => ({
    timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    utcOffsetMinutes: -new Date().getTimezoneOffset(),
    language: navigator.language,
    languages: navigator.languages,
})"""


def run(profile):
    with sync_playwright() as p:
        browser = p.chromium.launch(
            # Playwright's Chromium rejects SOCKS5 with username/password,
            # so use the HTTP proxy URL.
            proxy={
                "server": "http://" + GATEWAY,
                "username": PROXY_USER,
                "password": PROXY_PASS,
            },
            # Keep WebRTC from sending UDP outside the proxy.
            args=["--force-webrtc-ip-handling-policy=disable_non_proxied_udp"],
        )
        context = browser.new_context(
            locale=profile["locale"],
            timezone_id=profile["timezone"],
            geolocation=profile["geolocation"],
            permissions=["geolocation"],
        )
        page = context.new_page()

        # 1. Same sticky session, so the browser should exit from the same IP.
        page.goto("https://api.ipify.org")
        browser_ip = page.inner_text("body").strip()
        if browser_ip != profile["ip"]:
            raise RuntimeError("exit IP changed: session expired or not sticky")

        # 2. Read back what JavaScript sees.
        signals = page.evaluate(READ_SIGNALS)
        print(profile["country"], browser_ip, signals)

        browser.close()


if __name__ == "__main__":
    run(exit_profile())

Two notes from running this. Compare timezones by offset or normalized name, not raw strings: we passed Europe/Kyiv and Chromium reported the older alias Europe/Kiev. And granting geolocation lets every page read the coordinates without a prompt; if you don’t need location, leave geolocation and permissions out, since real users often deny it.

What this does not cover

Context options fix location signals only. The GPU string, fonts, the Chromium build’s TLS fingerprint and navigator.webdriver stay as they are. If a site offers an official API or data feed, that is usually more reliable than any browser setup. For running many such contexts, see our notes on designing a Playwright browser cluster, and for the protocol underneath, WebDriver BiDi vs CDP.

Browser Fingerprint Test Pages

Different tools answer different questions

A browser fingerprint test shows what a site can read from your setup. Each public tool measures something different, so use the one that matches your question and don’t compare scores across them.

Test pageWhat it showsUse it for
BrowserLeaksSeparate pages for IP, WebRTC, canvas, WebGL, fonts, TLS and moreChecking one layer at a time
CreepJSOpen-source research page that hunts for inconsistent or tampered valuesFinding contradictions
Cover Your TracksEFF’s tool (formerly Panopticlick); tracker blocking and fingerprint uniquenessPrivacy posture of a normal browser
AmIUniqueAcademic project comparing your attributes with its datasetSeeing which attributes are rare
tls.peet.wsJSON echo of your TLS, HTTP/2 and TCP/IP fingerprintComparing HTTP clients

Three cautions. Uniqueness scores depend on each tool’s visitor population, which skews privacy-conscious, so “unique” there predicts little about any given website. Test in the exact configuration you run, including headless mode, extensions and flags; your everyday browser says nothing about a Playwright worker. And test through the proxy.

Privacy and Regulation

Fingerprinting is not a legal loophole

Fingerprinting is harder for users to control than cookies, so regulators take it seriously. In the EU, the EDPB’s Guidelines 2/2023 on the technical scope of Article 5(3) of the ePrivacy Directive treat fingerprinting-style access to device information as within scope, so consent requirements can apply as they do for cookies. In the UK, the ICO responded in December 2024 to Google allowing fingerprinting by its advertising customers from 16 February 2025, calling the change irresponsible and stressing that data protection law, including PECR, applies. US state privacy laws such as California’s also treat persistent device identifiers as personal information.

Fraud prevention and security are widely recognized uses, and rules differ by jurisdiction and purpose. This is context, not legal advice; get advice for your own situation.

Frequently Asked Questions

Browser fingerprinting, in short
What is browser fingerprinting?

Identifying a browser from the characteristics it exposes (TLS handshake, headers, screen, fonts, timezone, canvas and WebGL output, network addresses) rather than from an identifier stored on the device. Many ordinary attributes combined can recognize a device across visits.

How is a fingerprint different from a cookie?

A cookie is stored in your browser and can be deleted. A fingerprint is recomputed on every visit, so clearing storage or opening a private window does not reset it.

Does a proxy stop browser fingerprinting?

No. A proxy changes the exit IP, ASN and location. TLS and HTTP/2 fingerprints, headers, JavaScript signals, WebRTC and behavior pass through unchanged, so the browser has to agree with the proxy.

What is a WebRTC leak?

WebRTC contacts a STUN server over UDP, which an HTTP proxy does not carry. The STUN server sees your real public IP, and any page can read it from the srflx candidate, so the site sees both the proxy IP and yours.

How do I run a WebRTC leak test?

Note your real IP with the proxy off, enable the proxy exactly as you use it, open a page such as browserleaks.com/webrtc and check that no WebRTC public IP is your real IPv4 or IPv6 address. Repeat after updates.

How do I stop WebRTC from leaking my IP in Chrome?

Set the WebRTC IP handling policy to disable_non_proxied_udp, either with the WebRtcIPHandling enterprise policy, the --force-webrtc-ip-handling-policy launch switch in automation, or an extension that offers WebRTC leak protection, such as the free ProxyEmpire Proxy Manager. Then confirm with a leak test.

Is canvas fingerprinting blocked by incognito mode?

Not in general. A private window has empty storage but the same fonts, graphics stack and GPU, so the canvas output is usually unchanged. Brave randomizes readouts; Tor Browser makes users report uniform values.

Give your browser an exit that matches it

Residential and mobile IPs with country, region and city targeting, sticky sessions for consistent profiles, and a free Chrome extension with WebRTC leak protection and matched timezone and language.

ProxyEmpire proxy types and prices

ProxyEmpire

Proxies for data collection at scale.

Residential, mobile, ISP and datacenter proxies for scraping, ad verification, SEO monitoring and multi-account work. Choose from 30M+ ethically sourced residential IPs and 4M+ 4G/5G mobile IPs in 170+ countries, target by country, city, ZIP, ISP or carrier, and connect over HTTP(S) or SOCKS5 with 99.9% uptime. Try residential and mobile proxies for $1.97.

🏘️ Rotating Residential Proxies
  • 30M+ Premium Residential IPs
  •  170+ Countries
    Every residential IP in our network corresponds to an actual desktop device with a precise geographical location. Our residential proxies are fast and reliable, with 99.9% uptime, and work for a wide range of use cases. You can use country, region, city, ZIP, ISP, ASN and OS fingerprint targeting for our rotating residential proxies, at no extra charge.

See our Rotating Residential Proxies

📍 Static Residential Proxies
  • 24 Countries
    Buy a dedicated static residential IP from one of the 24 countries that we offer proxies in. Keep the same IP for a month or longer, while benefiting from their fast speed and stability.

See our Static Residential Proxies

📳 Rotating Mobile Proxies
  • 4M+ Premium Mobile IPs
  •  170+ Countries
    Access millions of clean mobile IPs with precise targeting by country, region, city, ZIP, mobile carrier, ASN and OS fingerprint, at no extra charge. Get far fewer IP blocks and CAPTCHAs with our 4G and 5G proxies.

See our Mobile Proxies

📱 Dedicated Mobile Proxies
  • 4 Countries
  • 50+ Locations
    Get your own dedicated mobile proxy in one of our supported locations, with unlimited bandwidth and unlimited IP changes on demand. A great choice when you need a small number of mobile IPs and a lot of proxy bandwidth.

See our 4G & 5G Proxies

🌐 Rotating Datacenter Proxies
  • 197,000+ IPs Premium IPs
  •  62 Countries
    On a budget and need to do some simple scraping tasks? Our datacenter proxies are the perfect fit! Get started with as little as $2

See our Datacenter Proxies

♾️ Unlimited Residential Proxies
  • 1M+ Daily Residential IPs
  • 85+ Countries
    Our unlimited residential proxies bill by connection speed, not gigabytes: 100 to 1000 Mbit with unlimited bandwidth, on daily, weekly or monthly plans. Ask us for a 1-hour trial at 50 Mbit before you pick a plan.

See our Unlimited Residential Proxies

proxy locations

30M+ rotating IPs

99.9% uptime - high speed

99.9% uptime.

dedicated support team

24/7 Dedicated Support.

fair price

Fair Pricing.

🏠 Residential Proxies Rotating / Static / Unlimited
📱 Mobile Proxies Rotating and Dedicated
🖥️ Datacenter Proxies Rotating
🌍 IP Pool 30M+ residential + 4M+ mobile IPs
📶 Uptime 99.9% · Live status
💳 Payment Card · PayPal · Crypto · Bank transfer
💬 Support 24/7 live chat · [email protected]