Anti-Bot

What Is AudioContext Fingerprinting?

What Is AudioContext Fingerprinting? — conceptual illustration
On this page

AudioContext fingerprinting plays a silent waveform through the Web Audio API, then reads back the resulting floating-point samples and hashes them. In plain terms: a website tells your browser to process a sound it never plays, measures the exact numbers that come out, and squeezes them into a hash (a short fixed-length string). The output is determined by the exact audio subsystem - operating system audio mixer, DSP library (the code that does the audio math), CPU floating-point behaviour, and audio driver. Two devices with otherwise-identical fingerprints produce subtly different audio hashes, and headless browsers (browsers running with no visible window, common in automation) without a real audio stack produce a small set of giveaway values.

Quick facts

ReadsFloating-point output of a known oscillator + biquad filter chain
Why it worksAudio subsystem produces hardware/OS-dependent rounding differences
Headless tellOfflineAudioContext returns one of ~3 known hashes when no real audio stack exists
Used byPerimeterX, DataDome, Akamai (as one of dozens of signals)
FlagsVanilla headless Chrome, most stealth plugins

How the probe works

The standard probe uses OfflineAudioContext - a Web Audio API that renders samples without actually playing them, so the user hears nothing. The script creates an oscillator (a tone generator) at a known frequency (typically 1000 Hz), routes it through a DynamicsCompressorNode or BiquadFilterNode (audio effects, configured with known parameters), renders ~5000 samples, and hashes them with SHA-256.

The hash is stable per (browser, OS, CPU architecture, audio driver) and varies between them. In other words, the same setup always gives the same number, but a different setup gives a different one. Two iPhones produce the same hash; an iPhone and an Android produce different hashes; a real macOS user and a headless Chrome on the same machine produce different hashes because the audio fallback path is different.

The headless tell

Headless Chrome on a server with no audio device falls back to a stub audio backend (a placeholder with no real sound hardware behind it). The stub produces a small set of known hashes - roughly three distinct values across the entire population of headless Chrome instances on Linux servers. Anti-bot vendors maintain a blocklist of these specific hashes and flag any request matching them.

Even with --use-fake-device-for-media-stream and similar Chrome flags, the OfflineAudioContext path is independent of media-device flags. The fix is one of: (1) run on a machine with a real audio device passed through to the browser, (2) install a virtual audio driver (PulseAudio dummy sink, BlackHole on macOS) that produces real-machine-like output, or (3) use a tool that patches AudioContext at the engine level (Camoufox, CloakBrowser).

Why naive spoofing fails

Spoofing AudioContext in JavaScript is detectable in two ways. First, Function.prototype.toString() on the patched method reveals the patch - real Chrome's AudioContext.prototype.createOscillator.toString() returns "function createOscillator() { [native code] }", but a JS replacement returns the patch source instead, exposing the tampering. Second, the timing of OfflineAudioContext.startRendering() on a real audio stack vs a JS-stubbed one differs by orders of magnitude, and that timing is itself fingerprinted.

The only reliable mitigation is patching at the browser-engine level (below the JS layer) so toString() still returns [native code] and the render timing matches a real machine. This is what differentiates Camoufox / PatchRight from playwright-extra-plugin-stealth.

Code example

javascript
// The standard AudioContext probe used by major anti-bot vendors
async function audioFingerprint() {
  const ctx = new OfflineAudioContext(1, 5000, 44100);
  const osc = ctx.createOscillator();
  osc.type = 'triangle';
  osc.frequency.value = 1000;

  const compressor = ctx.createDynamicsCompressor();
  compressor.threshold.value = -50;
  compressor.knee.value = 40;
  compressor.ratio.value = 12;
  compressor.attack.value = 0;
  compressor.release.value = 0.25;

  osc.connect(compressor);
  compressor.connect(ctx.destination);
  osc.start(0);

  const buf = await ctx.startRendering();
  // sum a stable slice — anti-bot vendors usually hash samples 4500-5000
  let sum = 0;
  for (let i = 4500; i < 5000; i++) sum += Math.abs(buf.getChannelData(0)[i]);
  return sum.toString();
}
// Headless Chrome on Linux servers returns ~3 distinct values — easily recognized.

Related terms

Concept map

How AudioContext Fingerprinting connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

Why don't scrapers just disable the Web Audio API?

Disabling Web Audio means OfflineAudioContext returns undefined or throws an error, which is a stronger signal than any specific hash. Real browsers always have a working Web Audio API, so its absence stands out immediately. The detection asks "what does it produce", not "does it exist" - disabling is worse than failing the probe.

How distinctive is the audio hash compared to canvas?

Less distinctive between real users (collision rates around 1-in-1000 for popular configurations, meaning many real users share the same hash) but more distinctive between real and headless (the headless cluster is tiny and well-known). It is most useful as a headless-detection signal rather than a unique identifier.

Can I run a virtual audio device to fix this?

Yes. On Linux, PulseAudio with a dummy sink produces real-machine-like output, and on macOS BlackHole works similarly. The catch is that the hash needs to be coherent with the rest of the fingerprint - a Linux PulseAudio hash on a request claiming to be Windows is its own tell.

Last updated: 2026-05-31