← All articles

Audio fingerprinting explained, mechanically

5 min read fingerprintingaudioguide

A script builds a short audio processing graph, an oscillator feeding a compressor, renders it offline as fast as the CPU allows, and reads back the resulting sample buffer. Nothing is played through a speaker and no permission prompt appears, because offline rendering with the Web Audio API is pure computation, not playback.

The mechanism

OscillatorNode generates a periodic waveform, commonly a triangle wave, at a fixed frequency for a fixed duration. That signal is routed through a DynamicsCompressorNode with fixed parameters (threshold, knee, ratio, attack, release), the same node a mixing engineer uses to tame peaks, here repurposed because it is nonlinear: its gain reduction depends on continuous math applied per sample, so small differences in the underlying implementation do not average out over the buffer, they compound.

The whole graph runs inside an OfflineAudioContext, which renders as fast as possible rather than in real time and resolves with an AudioBuffer. The page reads getChannelData(0), a Float32Array, and reduces it to something short: a sum of absolute values over a slice of samples, a hash of the raw bytes, or specific sample indices read directly. The exact reduction varies between scripts. The source of the entropy does not.

Why the same signal renders to different numbers on different machines

Nothing about "run this oscillator through this compressor" fully determines the output at the bit level. The compressor's gain curve involves logarithms and exponentials, and how those are computed depends on the OS audio stack, the browser's internal DSP and resampling code, whether the CPU path uses SIMD, and floating point rounding behaviour that differs across hardware and software versions. Change any layer in that chain and the sample values shift, sometimes only in the last few bits, enough to change the hash completely.

That same chain does not change between two renders in one session on one machine. Same CPU, same OS audio stack, same browser build, every time, which is exactly why the value is stable for you and different for someone else. It measures a deterministic computation, not a random one.

Quieter than canvas, harder to fake

Fewer fingerprinting scripts bother with an audio probe than a canvas probe. Building a compressor graph and rendering it offline is more code than drawing text to a hidden canvas, and on its own the signal carries less entropy, which is why it usually shows up alongside canvas in a combined fingerprint rather than as the sole signal.

It is also harder to fake convincingly, for a specific reason. A believable canvas value only has to look like plausible pixel noise. An audio value has to be what a real compressor, running on a specific CPU and OS audio stack, would actually output, down to the floating point rounding. Producing a fake number that is merely plausible is easy. Producing one that stays internally consistent with everything else the profile claims about its hardware and OS is a different, much harder problem, since the entropy comes from implementation details that most spoofing tools never touch.

The same three failure modes as canvas

Absent. Overriding AudioContext or OfflineAudioContext so a method throws or returns nothing. Almost no real browser does this, so an absent audio fingerprint is itself a rare, identifying signal.

Randomized per call. Adding noise to the output on every render. Real hardware is deterministic, so a script renders the same graph twice in one session and compares. A mismatch confirms live tampering, a stronger finding than an unusual value.

Shared across profiles. Applying one fixed, plausible value to every profile a tool generates. It solves the randomization problem and creates a clustering one: identical audio hashes across thousands of otherwise-distinct sessions are exactly the pattern correlation analysis looks for.

These are the same three failure modes covered in more depth in the canvas fingerprinting post, because the underlying logic, deterministic per machine, varied across machines, is identical for both signals.

What holds up

A value produced by an actual rendering pipeline for that specific profile, applied in the browser engine itself, below the JavaScript layer, generated once per profile and persisted rather than recomputed or overridden by a script sitting on top of the real API.

Where a simpler tool is the right answer

If the goal is personal privacy on one browser rather than running many distinct profiles, Firefox's resist-fingerprinting mode normalizes AudioContext output as part of the same protection it applies to canvas, free, no extension required. Tor Browser inherits the same defence. Neither helps if you need several profiles that each need to look like a separate real machine, since normalizing toward a common value is the opposite of what that use case needs, but for a single person browsing under one identity it is a reasonable, no-cost answer.

Read your own value

Run this in your browser console:

async function audioHash() {
  const ctx = new OfflineAudioContext(1, 5000, 44100)
  const oscillator = ctx.createOscillator()
  oscillator.type = 'triangle'
  oscillator.frequency.value = 10000
  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
  oscillator.connect(compressor)
  compressor.connect(ctx.destination)
  oscillator.start(0)
  const buffer = await ctx.startRendering()
  const data = buffer.getChannelData(0)
  let sum = 0
  for (let i = 4500; i < 5000; i++) sum += Math.abs(data[i])
  return sum
}
audioHash().then(console.log)
audioHash().then(console.log)

The two logged numbers should match exactly on a real, unmodified browser. If you are testing an anti-detection tool and they differ, that is the tool randomizing output in real time, which is a more identifying finding than whatever value it was trying to hide.

CreepJS runs a version of this check alongside canvas and flags audio inconsistency as its own category. For the full set of properties expected to agree with each other, see the fingerprint consistency checklist and our overview of browser fingerprinting.

Try it on the free tier.

Unlimited local profiles, no credit card. Check it against the detectors yourself.