Developer10 min readFebruary 24, 2026

Device Fingerprinting for Multi-Account Detection: A Technical Deep-Dive

A comprehensive technical guide to device fingerprinting techniques including canvas fingerprinting, WebGL, audio context, font enumeration, and how to use them for detecting multi-account fraud.

IP addresses change. Cookies get cleared. Email addresses are free. So how do you reliably identify when the same person is creating multiple accounts on your platform? The answer is device fingerprinting, a collection of techniques that identify a device based on its unique combination of hardware and software characteristics.

This is a technical deep-dive into the most effective fingerprinting techniques available today, how they work under the hood, their strengths and limitations, and how BigShield combines them for multi-account detection that is both accurate and privacy-conscious.

What Makes a Device Unique?

No two devices are truly identical. Even two laptops of the same make and model will differ in installed fonts, GPU driver versions, screen calibration, timezone settings, and dozens of other properties. A device fingerprint collects these properties and hashes them into a stable identifier that persists across sessions, even without cookies.

The key insight for fraud detection: if two "different" accounts share the same device fingerprint, they are almost certainly controlled by the same person. And that is exactly the signal you need to catch multi-account abuse.

Canvas Fingerprinting

Canvas fingerprinting is one of the most powerful techniques because it leverages hardware-level differences in how devices render graphics.

How It Works

You draw a specific image to an HTML5 Canvas element, including text with specific fonts, colors, and shapes. Then you extract the pixel data. The result varies between devices because of differences in GPU hardware, driver versions, font rendering engines, anti-aliasing implementations, and sub-pixel rendering settings.

function getCanvasFingerprint(): string {
  const canvas = document.createElement('canvas');
  canvas.width = 240;
  canvas.height = 60;
  const ctx = canvas.getContext('2d');
  if (!ctx) return 'unsupported';

  // Draw text with specific styling
  ctx.textBaseline = 'top';
  ctx.font = '14px Arial';
  ctx.fillStyle = '#f60';
  ctx.fillRect(125, 1, 62, 20);

  ctx.fillStyle = '#069';
  ctx.fillText('BigShield fingerprint', 2, 15);

  ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
  ctx.fillText('Canvas test string', 4, 45);

  // Draw geometric shapes
  ctx.beginPath();
  ctx.arc(50, 50, 20, 0, Math.PI * 2, true);
  ctx.closePath();
  ctx.fill();

  return canvas.toDataURL();
}

Effectiveness and Limitations

Canvas fingerprints are remarkably stable across browser sessions and updates. They only change when the GPU driver is updated or the user switches to a different device. In our testing, canvas fingerprints alone can distinguish between devices with roughly 90% accuracy.

The limitation: some privacy-focused browsers (Brave, Tor Browser) add noise to canvas output or block it entirely. This means a blank or randomized canvas result is itself a signal, just not a differentiating one.

WebGL Fingerprinting

WebGL fingerprinting goes even deeper than canvas by querying the GPU directly for hardware and driver information.

Renderer and Vendor Strings

The WEBGL_debug_renderer_info extension exposes the GPU vendor and renderer strings. These are surprisingly specific. Instead of just "NVIDIA," you get something like "ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11 vs_5_0 ps_5_0, D3D11)." Combined with the WebGL version and supported extensions, this creates a detailed hardware profile.

function getWebGLFingerprint(): Record<string, string> {
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
  if (!gl) return { status: 'unsupported' };

  const debugInfo = (gl as WebGLRenderingContext)
    .getExtension('WEBGL_debug_renderer_info');

  const result: Record<string, string> = {
    vendor: gl.getParameter(gl.VENDOR),
    renderer: gl.getParameter(gl.RENDERER),
    version: gl.getParameter(gl.VERSION),
    shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
  };

  if (debugInfo) {
    result.unmaskedVendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
    result.unmaskedRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
  }

  // Collect supported extensions
  const extensions = gl.getSupportedExtensions() || [];
  result.extensionCount = extensions.length.toString();
  result.extensionHash = hashArray(extensions);

  return result;
}

WebGL Parameter Enumeration

Beyond vendor strings, WebGL exposes dozens of capability parameters: maximum texture size, maximum viewport dimensions, supported precision formats, aliased line width ranges, and more. Each parameter further narrows the fingerprint. Two devices might share the same GPU model but differ in driver-reported capabilities.

Audio Context Fingerprinting

This is one of the more surprising fingerprinting vectors. The Web Audio API processes audio through the device's audio stack, and minor differences in hardware and software produce measurably different output.

The Technique

Generate a tone using an OscillatorNode, process it through a DynamicsCompressorNode, and capture the output. The floating-point samples will vary slightly between devices due to differences in the audio processing pipeline, DSP implementation, and sample rate conversion.

async function getAudioFingerprint(): Promise<string> {
  const audioContext = new (window.AudioContext
    || (window as any).webkitAudioContext)();

  const oscillator = audioContext.createOscillator();
  oscillator.type = 'triangle';
  oscillator.frequency.setValueAtTime(10000, audioContext.currentTime);

  const compressor = audioContext.createDynamicsCompressor();
  compressor.threshold.setValueAtTime(-50, audioContext.currentTime);
  compressor.knee.setValueAtTime(40, audioContext.currentTime);
  compressor.ratio.setValueAtTime(12, audioContext.currentTime);
  compressor.attack.setValueAtTime(0, audioContext.currentTime);
  compressor.release.setValueAtTime(0.25, audioContext.currentTime);

  const analyser = audioContext.createAnalyser();
  analyser.fftSize = 2048;

  oscillator.connect(compressor);
  compressor.connect(analyser);
  analyser.connect(audioContext.destination);

  oscillator.start(0);

  await new Promise(resolve => setTimeout(resolve, 100));

  const dataArray = new Float32Array(analyser.frequencyBinCount);
  analyser.getFloatFrequencyData(dataArray);

  oscillator.stop();
  await audioContext.close();

  // Hash the frequency data
  return hashFloat32Array(dataArray);
}

Stability and Uniqueness

Audio fingerprints are less unique than canvas or WebGL fingerprints on their own, but they add a valuable independent signal. They are most useful for distinguishing between different hardware platforms (a MacBook Pro vs. a ThinkPad vs. a smartphone). Combined with other fingerprint vectors, they significantly reduce false positives.

Screen and Display Fingerprinting

The display configuration is another rich source of fingerprint data:

  • Screen resolution and color depth: window.screen provides resolution, available resolution (minus taskbar), color depth, and pixel depth.
  • Device pixel ratio: window.devicePixelRatio reveals the scaling factor, which differs between devices and display settings.
  • HDR capability: Media queries can detect HDR support, P3 color gamut support, and other display features.
  • Touch support: The combination of maxTouchPoints, the presence of touch events, and hover capability creates a device-type signal.
function getDisplayFingerprint(): Record<string, string | number | boolean> {
  return {
    screenWidth: screen.width,
    screenHeight: screen.height,
    availWidth: screen.availWidth,
    availHeight: screen.availHeight,
    colorDepth: screen.colorDepth,
    pixelRatio: window.devicePixelRatio,
    touchPoints: navigator.maxTouchPoints,
    hdrSupport: window.matchMedia('(dynamic-range: high)').matches,
    p3ColorGamut: window.matchMedia('(color-gamut: p3)').matches,
    prefersReducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
    prefersColorScheme: window.matchMedia('(prefers-color-scheme: dark)').matches
      ? 'dark' : 'light',
  };
}

Font Enumeration

Every device has a different set of installed fonts, and this set is surprisingly unique. While the old technique of using Flash or Java to enumerate fonts directly is obsolete, modern approaches use CSS measurement to detect font presence.

The Measurement Approach

Render a string in a fallback font and measure its dimensions. Then render the same string in a test font with the fallback, and compare. If the dimensions change, the test font is installed.

function detectFonts(testFonts: string[]): string[] {
  const baseFonts = ['monospace', 'sans-serif', 'serif'];
  const testString = 'mmmmmmmmmmlli';
  const testSize = '72px';

  const span = document.createElement('span');
  span.style.fontSize = testSize;
  span.style.position = 'absolute';
  span.style.left = '-9999px';
  span.textContent = testString;
  document.body.appendChild(span);

  const baseWidths = new Map<string, number>();
  for (const base of baseFonts) {
    span.style.fontFamily = base;
    baseWidths.set(base, span.offsetWidth);
  }

  const detected: string[] = [];
  for (const font of testFonts) {
    for (const base of baseFonts) {
      span.style.fontFamily = `"${font}", ${base}`;
      if (span.offsetWidth !== baseWidths.get(base)) {
        detected.push(font);
        break;
      }
    }
  }

  document.body.removeChild(span);
  return detected;
}

Testing against a list of 60-80 common fonts provides enough entropy to be useful without taking too long. The resulting font list is highly device-specific, especially on desktop systems where users install design software, development tools, and other applications that bundle custom fonts.

Timezone, Language, and Platform Signals

These are low-entropy signals individually, but they are valuable in combination:

  • Timezone offset and name: Intl.DateTimeFormat().resolvedOptions().timeZone gives the IANA timezone name. Combined with the numeric offset, this is more specific than either alone.
  • Languages: navigator.languages returns the full language preference list, not just the primary language. A user with ["en-US", "pt-BR", "pt"] has a very different profile from one with just ["en-US"].
  • Platform inconsistencies: A user-agent claiming to be Windows but reporting a MacOS-only font list is a red flag. These inconsistencies suggest spoofing and are themselves a signal of fraud.

Combining Signals for Multi-Account Detection

No single fingerprinting technique is perfect. Canvas can be spoofed. Fonts change when software is installed. Screen resolution changes when a monitor is swapped. The power comes from combining all of these into a composite fingerprint with a fuzzy matching algorithm.

Weighted Composite Scoring

At BigShield, we assign weights to each fingerprint component based on its stability and uniqueness:

interface FingerprintMatch {
  component: string;
  weight: number;
  matches: boolean;
}

function calculateFingerprintSimilarity(
  a: DeviceFingerprint,
  b: DeviceFingerprint
): number {
  const components: FingerprintMatch[] = [
    { component: 'canvas', weight: 0.20, matches: a.canvasHash === b.canvasHash },
    { component: 'webgl', weight: 0.18, matches: a.webglHash === b.webglHash },
    { component: 'audio', weight: 0.10, matches: a.audioHash === b.audioHash },
    { component: 'fonts', weight: 0.15, matches: fontSetSimilarity(a.fonts, b.fonts) > 0.9 },
    { component: 'screen', weight: 0.10, matches: a.screenHash === b.screenHash },
    { component: 'timezone', weight: 0.05, matches: a.timezone === b.timezone },
    { component: 'languages', weight: 0.08, matches: arraysEqual(a.languages, b.languages) },
    { component: 'platform', weight: 0.07, matches: a.platform === b.platform },
    { component: 'webglParams', weight: 0.07, matches: a.webglParamsHash === b.webglParamsHash },
  ];

  const totalWeight = components.reduce((sum, c) => sum + c.weight, 0);
  const matchWeight = components
    .filter(c => c.matches)
    .reduce((sum, c) => sum + c.weight, 0);

  return matchWeight / totalWeight;
}

A similarity score above 0.75 strongly suggests the same device. Above 0.90, it is nearly certain. We flag accounts that share a device fingerprint above the threshold for manual review or automatic action, depending on the customer's configuration.

Handling Fingerprint Evolution

Devices change over time. A browser update might alter the canvas output. A new monitor changes the screen resolution. A software installation adds fonts. Good multi-account detection needs to handle this "fingerprint drift" without losing track of the device.

The solution is to track fingerprint components independently and use a sliding window of recent fingerprints for each account. If Account A's current fingerprint shares 6 out of 9 components with Account B's fingerprint from two weeks ago, that is still a strong match, even if some components have drifted.

Privacy Considerations

Device fingerprinting raises legitimate privacy concerns. Here is how to use it responsibly:

  • Disclose it. Your privacy policy should mention that you collect device characteristics for fraud prevention.
  • Limit scope. Use fingerprinting only for fraud detection, not for advertising or tracking across unrelated services.
  • Hash everything. Store fingerprint hashes, not raw data. You need to compare fingerprints, not reconstruct a user's font list.
  • Respect opt-outs. If a user blocks fingerprinting (e.g., via Brave's shields), fall back to other signals rather than treating the block itself as suspicious.

Fighting Fingerprint Spoofing

Sophisticated fraudsters use tools like anti-detect browsers (Multilogin, GoLogin, Dolphin Anty) that generate unique fingerprints for each browser profile. These tools are specifically designed to defeat fingerprinting.

Detecting spoofed fingerprints requires looking for inconsistencies. An anti-detect browser might report a rare GPU model but fail to replicate the corresponding WebGL parameter limits. Or it might claim a specific set of fonts that does not match the reported operating system. These mismatches become signals of their own.

For more on detecting sophisticated bot behavior, including post-signup behavioral analysis that catches what fingerprinting misses, check out our article on catching bot behavior through post-signup detection. And for understanding how fingerprinting fits into broader coordinated fraud detection, see our campaign attribution guide.

How BigShield Uses Device Fingerprinting

BigShield collects a lightweight device fingerprint through our JavaScript SDK. The fingerprint is computed client-side, hashed, and sent alongside the email validation request. On our end, we compare it against fingerprints associated with previously flagged accounts and known fraud campaigns.

The fingerprint does not determine the score alone. It is one of 20+ signals that feed into our scoring pipeline. But for multi-account detection specifically, it is one of the most valuable signals we have. If you are building a platform where multi-accounting is a problem (free tier abuse, referral fraud, ban evasion), device fingerprinting combined with BigShield's other signals gives you a serious edge. Try it at bigshield.app.

Ready to stop fake signups?

BigShield validates emails with 20+ signals in under 200ms. Start for free, no credit card required.

Get Started Free

Related Articles