How the leak works
WebRTC is the browser API for peer-to-peer audio, video, and data connections — think browser video chat. For two peers to connect when both sit behind a home router (NAT — the address translation that lets many devices share one public IP), WebRTC needs to discover the addresses they can be reached at. It does this with the ICE protocol, which gathers candidate IPs from three places: your local network interface, your public IP via STUN servers (servers whose only job is to tell you what your public IP looks like from the outside), and TURN relays. Any web page can run this:
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
pc.createDataChannel('');
pc.createOffer().then(o => pc.setLocalDescription(o));
pc.onicecandidate = (e) => { if (e.candidate) console.log(e.candidate.candidate); };The returned ICE candidates include your real IP, even if all your HTTP traffic is going through a proxy. The proxy hides your HTTP requests, but WebRTC went around it.
The 5-vector coherence test
Modern anti-bots check whether five different signals all tell the same geographic story. If you claim to be in one place, all five should agree:
- IP country — the country of your proxy's exit IP.
- Timezone — what the browser reports via
Intl.DateTimeFormat().resolvedOptions().timeZone. - Accept-Language header — your stated language preferences.
- WebRTC ICE candidate — the network the browser is actually connecting from (the leaked IP).
- DNS resolver location — which DNS server looked up the page's domain.
Picture a US proxy paired with an Accept-Language of ur-PK (Urdu, Pakistan), a timezone of Asia/Karachi, and a Pakistani WebRTC candidate — it fails immediately. It does not matter how good the proxy is; the contradiction between the vectors is itself the signal. This is why "use a US datacenter proxy and call it a day" stopped working around 2021.
Mitigation by tool
Camoufox with geoip=True looks up the country of your proxy exit IP, then sets timezone, locale, language, WebRTC ICE policy, and DNS to all match it. That one flag fixes the most common coherence failure in seconds. Playwright / Puppeteer need you to do this by hand — set locale, timezone_id, and Accept-Language yourself, and either disable WebRTC explicitly or route it through the proxy. HTTP scraping (curl_cffi, tls-client) has no WebRTC at all, so this vector never fires — part of why HTTP scraping beats browser scraping on many targets. Self-test: browserleaks.com/webrtc shows you exactly what WebRTC exposes from your setup. Run your browser context against it before you deploy.
