1. Read the composed machine
--fingerprint-explain prints the machine a launch presents and exits without opening a window. Pass the same switches the launch uses:
! explain a row, and the limitations list at the end names what the launch could not apply on this host. Quickstart shows the value rows, and Seeds and identity explains the seed lines.
The explain output comes from the composition. It does not show what a page reads, and it does not know the locale and timezone a package would look up. The next step shows both.
2. Read what a page sees
what_a_page_sees.py launches a persona and serves it a page from a local HTTP server. The page reads its values, starts a worker that reads them again, fetches the request headers the browser sent, and posts everything back. The script prints the result as JSON.
--locale and --timezone, the package looks them up from your IP address. --headed uses the package’s Xvfb on a server. Output from a Debian 13 container on an arm64 host, apostate 0.4.3:
- Page, worker and headers agree. The User-Agent, platform, cores, memory, languages, timezone and WebGL renderer in
workermatch the page’s. Theuser-agentandaccept-languageheaders matchnavigator, andsec-ch-ua-platformmatchesuserAgentData.platform. - Locale and timezone match the exit. Behind a proxy,
intlandaccept-languageshould belong to the proxy’s location. Locale and timezone covers the lookup. - The persona’s fonts are present. A Windows persona reads
truefor Segoe UI and Calibri andfalsefor the macOS and Linux families.falsefor Segoe UI means the Windows fonts are not installed on the host. Fonts covers installing them. webgpuis notnull. On Linux it isnullin headless launches and in headed launches as root. Linux servers compares the modes, and Known gaps tracks the gap.architectureis the host’s. It readsarmhere because the host is arm64, andx86in an amd64 container. No real Windows machine pairsarmwith this GPU, and iphey.com flags the pair. Hosts explains why.availableis smaller thansizefor a Windows persona, by the taskbar.
viewport is 889 high here, where a headless launch reads 945. The 0.4.3 package passes --no-sandbox, so this headed launch shows Chromium’s unsupported-flag bar on its first tab, which takes 56 pixels. From 0.4.4 the package keeps the sandbox on where it runs and leaves the flag out. Known gaps has the workaround.
The script checks five font families by comparing text widths. document.fonts.check() returns true for every family name, installed or not, so it cannot tell.
3. Open public test pages
Public test pages read more values than the script, and some compare them with what real browsers report. Open them in a launch with the same persona, proxy and options you use for real work, because several of them also judge the IP address.
WebRTC covers checking for WebRTC leaks.
4. Measure FingerprintJS Pro
scripts/measure-fpjs.py launches one persona headed through a proxy, loads the FingerprintJS Pro playground and prints the exit’s address, country and timezone, the suspect score and each flag behind it. It runs from a checkout of the repository and needs curl and a residential proxy. On a server it uses the package’s Xvfb.
--platform host measures the real machine for comparison, --out FILE saves the full response, and APOSTATE_BINARY points it at a local build. FingerprintJS has dated results.
Next steps
The test suite checks what pages read on a real browser, in every context, and opens public detector pages beside control browsers. It writes every result to a file.Test suite
What the offline and live tiers check, and how to read the outcomes.
Latest results
The offline checks and public detector pages from the newest run on each host.