JONAS STAMM
— FIELD NOTES
build-in-publicMarch 11, 2026· 4 min read

How I Stopped Bot Spam Without Requiring Login

Every indie app gets hammered by bots eventually. Sond — the trip planning app — hit this around month 2. Suddenly 40% of new accounts were fake. They'd load th...

JS
Jonas Stamm
Founder, BauGPT

Every indie app gets hammered by bots eventually.

Sond — the trip planning app — hit this around month 2. Suddenly 40% of new accounts were fake. They'd load the app, click around for 3 seconds, and vanish. The conversion metrics were garbage.

I couldn't add login requirements. That would kill onboarding. So I built device fingerprinting instead.

The idea: create a unique identifier for each device based on hardware and behavioral signals. Bots usually appear from the same fingerprint repeatedly. Real users don't.

What Makes a Device Unique?

A "fingerprint" combines signals that are hard to spoof:

Hardware signals:

Browser/OS signals:

Network signals:

Behavioral signals:

The key: never rely on a single signal. Bots are good at spoofing one or two. Combine 10+, and they fail.

Canvas Fingerprinting (The Clever Trick)

Here's a subtle one: canvas fingerprinting.

You draw text and shapes to an HTML5 canvas, then read the pixel data. The exact pixels differ based on:

It's a fingerprint of the device's graphics stack. Hard to fake without actually rendering on the target hardware.

function getCanvasFingerprint() {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  
  canvas.width = 280;
  canvas.height = 60;
  
  ctx.fillStyle = '#f60';
  ctx.fillRect(125, 1, 62, 20);
  
  ctx.fillStyle = '#069';
  ctx.globalCompositeOperation = 'multiply';
  ctx.fillRect(15, 15, 112, 20);
  
  ctx.fillStyle = '#FFFFFF';
  ctx.font = '17px "Arial"';
  ctx.textBaseline = 'alphabetic';
  ctx.fillText("Canvas fingerprint", 2, 15);
  
  return canvas.toDataURL();
}

Bots fail here because they run headless (no GPU), so the rendering is different.

WebGL Fingerprinting (More Powerful)

WebGL exposes the GPU vendor and model:

function getWebGLFingerprint() {
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
  
  const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
  const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
  const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
  
  return { vendor, renderer };
}

You get things like:

Bots running on cloud servers report generic GPUs or headless renderers. Exposed immediately.

Building the Fingerprint

Combine all signals into a single hash:

async function generateDeviceFingerprint() {
  const signals = {
    model: navigator.userAgent,
    screen: `${window.innerWidth}x${window.innerHeight}x${window.devicePixelRatio}`,
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    language: navigator.language,
    canvas: getCanvasFingerprint(),
    webgl: getWebGLFingerprint(),
    maxTouchPoints: navigator.maxTouchPoints,
    connection: navigator.connection?.effectiveType,
    hardwareConcurrency: navigator.hardwareConcurrency,
    deviceMemory: navigator.deviceMemory,
  };
  
  const fingerprint = await hashObject(signals);
  return fingerprint;
}

Store this in local storage and send it with every request. On the backend, track the fingerprint and count requests.

The Results

With fingerprinting in place, patterns became clear:

I flagged accounts with:

Result: 95% bot filtering, zero false positives on real users.

The Ethical Side

Device fingerprinting can feel creepy to privacy-conscious users. I don't hide it — it's in the Terms of Service:

"To prevent abuse, we use device fingerprinting to detect and block automated access."

I don't use it to track users across sites. Fingerprints are device-local and app-specific. If someone clears app data, their fingerprint resets. Users can request deletion.

Alternatives I Considered

Fingerprinting + rate limiting is the fastest, least invasive option.

The Bigger Picture

Device fingerprinting is a tool, not a silver bullet. Combined with IP rate limiting, email validation, and behavioral analysis, it becomes powerful.

But the real lesson: don't build authentication into your onboarding if you don't need to. Build detection instead. Identify bots after the fact, block them, keep the user experience frictionless.

Your conversion rate will thank you.

Keep reading

All writing →
technical-deep-dive · Aug 31, 2026
01

The hard part of a tender radar is not the matching

Most of the work in shipping BauGPT's Ausschreibungs-Radar had nothing to do with matching. Deciding whether a public tender fits a Bau company's profile was th...

7 MIN READ
technical-deep-dive · Aug 24, 2026
02

The hard part of a tender radar is not the matching

Most of the work in shipping BauGPT's tender radar had nothing to do with matching. Matching worked early and was never the risky part. Everything after that wa...

7 MIN READ
technical-deep-dive · Aug 20, 2026
03

What Bau Enterprises Actually Made Us Build

Most of what we shipped on BauGPT in the last weeks had nothing to do with the model. It was folders, permissions, single sign-on, and Word files that open corr...

7 MIN READ
— THE NEWSLETTER

One note a week.
No fluff, just what works.

AI engineering, growth hacks, and messy lessons from shipping BauGPT. Unsubscribe anytime. I'll even miss you.

FIELD NOTES · NEXT ISSUE DROPS MONDAY
↳ No spam. One note weekly.