Hardware-Class Inference: How Render and Compute Timing Reveals Your Real Machine Tier
Three weeks ago I watched a QA engineer's eight test profiles all get identified as the same machine in a single session. All the obvious stuff was handled — different canvas hashes, different WebGL renderer strings, different hardwareConcurrency values ranging from 4 to 16. Each profile claimed to be a different machine.
The problem? Every profile completed the same WebGL benchmark in 1.8-2.1ms.
Eight "different computers" with eight different CPU/GPU configurations, all rendering at RTX 4080 speeds. That's not how hardware works. The detection script noticed. I felt genuinely bad for the guy — he'd done everything right except the one thing that mattered.
This is timing-based hardware fingerprinting, and it's the detection vector that exposes profiles where everything else looks perfect. You can spoof the navigator APIs all day. You can't spoof physics. If you're running separate profiles for testing or client work — or managing click fraud monitoring with ClickzProtect — this timing leak will skew your results.
What We're Building
By the end of this tutorial, you'll understand how sites infer your real hardware class through WebGL render timing and WASM compute benchmarks. You'll know which timing signals get checked, why spoofing navigator.hardwareConcurrency without matching timing is worse than not spoofing at all, and how to configure antidetect profiles that don't contradict themselves.
We'll test against CreepJS and document the timing patterns that mark synthetic profiles.
Prerequisites
- Basic understanding of browser fingerprinting (if you're new, start with our antidetect browser myths post)
- Access to an antidetect browser with hardware spoofing — JustBrowser has a 7-day trial with everything unlocked
- Chrome DevTools proficiency (we'll use Performance tab)
- A machine where you can run timing tests without background interference
Step 1: Understanding Timing-Based Hardware Inference
Here's the fundamental issue. navigator.hardwareConcurrency tells websites your CPU core count. navigator.deviceMemory tells them your RAM. These are JavaScript properties. Spoofing them is trivial — override the getter, return whatever number you want.
But timing? Timing is physics. And physics doesn't care about your JavaScript overrides.
// Run a parallel compute benchmark
const iterations = 10000000;
const workers = navigator.hardwareConcurrency;
const start = performance.now();
// Spawn workers, run math operations, wait for completion
const elapsed = performance.now() - start;
console.log(`${workers} cores completed ${iterations} iterations in ${elapsed}ms`);
If you claim 4 cores but the benchmark completes at 16-core speed, you've created an impossible machine. The claimed hardwareConcurrency says one thing. The actual compute throughput says another. Detection systems compare the two.
Same deal with GPU timing. WebGL render operations take predictable amounts of time based on your GPU tier:
| GPU Class | Typical Shader Compile | Draw Call Throughput |
|---|---|---|
| Intel UHD 620 | 15-25ms | 2,000-4,000/frame |
| NVIDIA GTX 1650 | 8-12ms | 8,000-12,000/frame |
| NVIDIA RTX 3060 | 4-7ms | 15,000-25,000/frame |
| NVIDIA RTX 4090 | 1-3ms | 40,000+/frame |
| Apple M2 (integrated) | 6-10ms | 10,000-18,000/frame |
If your WebGL renderer string claims Intel UHD 620 but your draw calls complete at RTX 4090 speeds, something's obviously wrong. (We covered the WebGL renderer spoofing fundamentals in our WebGL fingerprinting tutorial — this post builds on those concepts.)
Step 2: The Benchmarks Sites Actually Run
Detection services don't just query your hardwareConcurrency and trust it. They validate it. I spent way too long figuring this out the hard way.
WebGL Shader Compilation Time
const gl = canvas.getContext('webgl2');
const shaderSource = `
precision highp float;
// Complex shader with loops and texture samples
`;
const start = performance.now();
const shader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
const compileTime = performance.now() - start;
Shader compilation happens on the GPU. Different GPUs compile at different speeds. A profile claiming an Intel integrated GPU but compiling shaders in 2ms (NVIDIA territory) is flagged.
WASM Compute Loops
const wasmModule = await WebAssembly.instantiateStreaming(fetch('benchmark.wasm'));
const benchmark = wasmModule.instance.exports;
const start = performance.now();
benchmark.runIntegerBenchmark(10000000);
const elapsed = performance.now() - start;
WebAssembly runs close to native speed. The execution time directly reflects CPU capability. A machine claiming 4GB RAM and 4 cores shouldn't execute a compute-intensive WASM module at server-grade speeds.
Canvas Rendering Speed
const ctx = canvas.getContext('2d');
const start = performance.now();
for (let i = 0; i < 10000; i++) {
ctx.fillRect(Math.random() * 100, Math.random() * 100, 10, 10);
ctx.arc(50, 50, 20, 0, Math.PI * 2);
ctx.fill();
}
const elapsed = performance.now() - start;
Canvas operations are CPU-bound on 2D context, GPU-accelerated on WebGL. Either way, timing corresponds to hardware class. Fast machine, fast rendering. Slow machine spoofed as fast? The math doesn't add up — and honestly, this is where I've seen the most operators trip up.
Worker Spawn Latency
const workerCount = navigator.hardwareConcurrency;
const start = performance.now();
const workers = [];
for (let i = 0; i < workerCount; i++) {
workers.push(new Worker('worker.js'));
}
// Measure time to spawn and initialize all workers
Spawning 16 Web Workers takes longer than spawning 4. If you claim hardwareConcurrency of 4 but your Worker spawn latency matches a 16-core machine (because you're actually running on 16 cores), that's a tell.
Step 3: Why Navigator Spoofing Without Timing Coherence Fails
I'll be direct about this, even though it's going to annoy some people. If you're spoofing hardwareConcurrency and deviceMemory but leaving timing unchanged, you're making yourself more detectable, not less. I know that sounds backwards. Here's why.
Real users have consistent hardware profiles. Their CPU cores, RAM, GPU, and performance characteristics all match. A gaming PC with an RTX 4090 and 32GB RAM runs benchmarks at gaming PC speeds. A $400 laptop with Intel UHD and 8GB RAM runs benchmarks at $400 laptop speeds. There's no contradiction.
A spoofed profile that claims $400 laptop specs but performs like a gaming PC is screaming "I'm running antidetect software on a powerful machine." That's a stronger signal than just running with your real specs.
The issue compounds with VPS deployments. A typical VPS runs on server-grade hardware — 32+ logical cores, fast NVMe, beefy RAM. If you spin up antidetect profiles on a VPS and spoof each one as a consumer laptop, the timing doesn't match. Your "Intel i5 laptop" profile completes WASM benchmarks in 50ms when a real i5 laptop takes 200ms.
Detection teams know VPS timing signatures. They've seen thousands of them. The combination of "claims consumer hardware" + "performs like server hardware" is a well-known pattern. Frustrating? Absolutely. But pretending it isn't real won't help you. For teams tracking privacy-first analytics with JustAnalytics, this same timing data shows up in performance metrics and can correlate sessions.
Step 4: Testing Your Profile's Timing Coherence
Before running any profile operationally, test whether the timing matches the claimed hardware. Here's the process:
Step 4.1: Baseline Your Real Hardware
Open a clean browser (no antidetect, no spoofing) and run these benchmarks:
// Save this as timing-test.html and open it
const results = {};
// Test 1: WASM-style compute
const start1 = performance.now();
let x = 0;
for (let i = 0; i < 50000000; i++) {
x += Math.sqrt(i) * Math.sin(i);
}
results.computeLoop = performance.now() - start1;
// Test 2: Canvas operations
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 500;
const ctx = canvas.getContext('2d');
const start2 = performance.now();
for (let i = 0; i < 5000; i++) {
ctx.beginPath();
ctx.arc(Math.random() * 500, Math.random() * 500, 20, 0, Math.PI * 2);
ctx.fill();
}
results.canvasOps = performance.now() - start2;
console.table(results);
console.log('hardwareConcurrency:', navigator.hardwareConcurrency);
console.log('deviceMemory:', navigator.deviceMemory);
Record the results. This is your actual hardware performance.
Step 4.2: Run the Same Test on Your Antidetect Profile
Now open the same test in your antidetect profile. Compare:
- Does the compute loop time match what you'd expect from the claimed hardwareConcurrency?
- Does the canvas ops time match the claimed GPU?
- If you're claiming a slower machine but the benchmark runs faster, you've got a coherence problem.
Step 4.3: Check Against CreepJS
CreepJS doesn't explicitly label "timing mismatch" as a lie, but it does measure performance.now() behavior and compare it against expected patterns. Look at the performance section. If your timing signature is wildly inconsistent with your claimed hardware class, it'll show.
Step 5: Fixing Timing Incoherence
You've got three options when your timing doesn't match your claimed hardware.
Option 1: Claim Hardware That Matches Your Real Machine
The simplest fix. If you're running on a Ryzen 9 with an RTX 4070, don't claim to be an Intel i3 with integrated graphics. Configure profiles that claim similar-tier hardware. The timing will naturally match because you're not lying about your performance class.
This limits your flexibility — you can't pretend to be a budget laptop if you're on a gaming rig — but it's the most reliable approach. Sometimes the boring answer is the right one.
Option 2: Use Hardware You're Actually Claiming
If you need profiles that look like budget consumer machines, run them on budget consumer machines. Buy a few refurbished laptops. Spin up cloud instances with constrained resources. The timing matches because the hardware actually is what you claim.
This is expensive and logistically annoying, but it works. Enterprise QA and device-testing teams sometimes maintain hardware fleets for exactly this reason.
Option 3: Use an Antidetect Browser That Spoofs Hardware Signals Natively
This is where native engine modifications matter. (Full disclosure: this is what JustBrowser does, and I'm obviously biased. Take what follows with that in mind.)
JustBrowser spoofs the WebGL vendor/renderer strings, hardwareConcurrency, deviceMemory and the cpuPerformance tier at the C++ level so they agree with each other. It does not throttle render or compute timing — pick a claimed hardware class that is plausible for the machine you actually run on.
Extension-based or wrapper-based antidetect can spoof what the JavaScript APIs return, but nothing changes how fast the GPU actually renders. The timing leaks through whenever the claimed class sits far from the real one.
JustBrowser keeps the reported hardware signals (hardwareConcurrency, deviceMemory, cpuPerformance tier, WebGL strings) consistent per profile at the engine level. Timing coherence is on you: match the claimed class to the hardware you run on. There's one plan — $9.99/month, or $99.99/year — with unlimited profiles. The 7-day trial holds nothing back, so you can test the concept before you pay for it.
Common Errors and How to Fix Them
Error: WebGL benchmarks complete 10x faster than expected for claimed GPU
You're claiming integrated graphics but running on a discrete GPU. Classic mistake. Either change the claimed GPU to match your actual hardware class, or use an antidetect browser that throttles render timing to match the profile. Most wrapper-based tools can't do this.
Error: WASM compute benchmarks complete inconsistently
This usually means background processes are affecting timing. Close other applications, disable browser extensions, run the test multiple times. Inconsistent timing is itself a fingerprint signal — real hardware produces consistent results.
Error: Worker spawn time doesn't match claimed core count
You're claiming 4 cores but spawning workers at 16-core speed (because you have 16 cores). Either match the claimed core count to your actual cores, or use a tool that limits worker concurrency to match the profile. Note that limiting workers breaks some legitimate functionality — sites expect Worker performance to match hardwareConcurrency.
Error: Profile passes individual tests but fails platform detection anyway
Platforms run more sophisticated timing analysis than public tools like CreepJS. They correlate timing across multiple vectors — WebGL, WASM, canvas, network request parallelism, DOM manipulation speed. Passing any single test doesn't guarantee you pass them all. Audit the entire timing surface, not just one benchmark. If you're also dealing with click fraud issues, ClickzProtect can help monitor those patterns separately.
The VPS Problem: A Rant
Quick tangent, because I see this constantly and it drives me up the wall.
Running antidetect profiles on a VPS and wondering why they show up as server hardware? This is probably why. Server hardware doesn't time like consumer hardware. Period. I've had this exact conversation maybe fifty times.
A $5/month VPS on Hetzner or DigitalOcean runs on Xeon or EPYC processors with 2.5GHz+ base clocks. Your $400 laptop runs on a mobile i5 with 1.6GHz base and turbo to maybe 4GHz under specific thermal conditions. These machines don't perform the same. They don't time the same. No amount of navigator spoofing changes that.
If you need consumer-grade timing signatures, you need consumer-grade hardware. There's no shortcut. Teams that run many test profiles at scale either use real device farms or use native antidetect tools that can produce coherent timing — and even then, they match profile claims to plausible hardware tiers. Each platform's own rules on multiple accounts still apply.
This is a hard constraint. I don't like it either — I wish there was a clever workaround. But ignoring it doesn't make it go away.
Next Steps
Timing-based hardware inference is one piece of the fingerprint stack. For complete profile consistency, you also need:
- WebGL parameter matching — renderer string, texture limits, shader precision all need to match the claimed GPU (covered in our WebGL fingerprinting guide)
- Audio fingerprint coherence — AudioContext timing also varies by hardware (covered in our audio fingerprinting post)
- Font and locale consistency — your font list should match the claimed OS
- Proxy quality — residential proxies that match the claimed locale, not datacenter IPs with consumer hardware spoofing
If you're running automation at scale with Playwright or Puppeteer, check our REST API Selenium automation tutorial for integrating these profiles into your pipeline. Teams doing ad verification or geo-specific testing can pair fingerprint-consistent profiles with ClickzProtect for click quality monitoring or JustAnalytics for privacy-respecting attribution.
Test your current setup against the timing benchmarks above. If your profiles claim one thing but perform like another, now you know why things keep breaking.
Frequently Asked Questions
How do websites infer hardware class from timing?
Sites run micro-benchmarks using WebGL render passes and WASM compute loops, then measure how long they take. A high-end RTX 4090 completes a WebGL shader test in 2-3ms. An Intel UHD 620 takes 15-25ms. These timing differences reveal your actual hardware tier regardless of what navigator.hardwareConcurrency reports. The math doesn't lie — if your profile claims 4 CPU cores but executes a parallel WASM benchmark at 16-core speeds, something's wrong.
Can I just throttle my CPU to fake slower hardware?
Technically yes, but it's impractical. You'd need to throttle consistently across all operations — not just the fingerprint tests. Real users don't have perfectly throttled machines. Inconsistent throttling (fast page loads, slow benchmarks) is itself detectable. The better approach is matching your spoofed hardware profile to hardware you actually have.
Which benchmarks do detection services actually run?
The main ones are WebGL shader compilation time, WebGL draw-call throughput, WASM integer/float math loops, canvas rendering speed, and Worker spawn latency. FingerprintJS Pro measures several of these. CreepJS checks performance.now() precision and timing consistency. Enterprise detection (Cloudflare, PerimeterX, HUMAN) runs more sophisticated timing analysis that isn't publicly documented.
Does JustBrowser handle timing fingerprinting?
JustBrowser spoofs the hardware-facing APIs (WebGL vendor/renderer, hardwareConcurrency, deviceMemory, cpuPerformance tier) natively and keeps them consistent per profile. It does not alter real render or compute speed, so choose a hardware class close to your actual machine tier.
Try JustBrowser
Native Chromium antidetect browser — not extension-based. Real C++ engine patches at the canvas / WebGL / audio / font / screen layer, so 40+ identity parameters are genuine, not faked. REST API for Playwright, Puppeteer, Selenium. $9.99/month or $99.99/year. 7-day free trial, card required — cancel any time in the seven days and you are not charged. Unlimited profiles.
Get started → · How it differs from Multilogin / GoLogin / AdsPower
Ready to manage multiple accounts?
Seven days free, then $9.99/month — one plan, everything included.