Skip to content
JONAS STAMM
— FIELD NOTES
build-in-public— March 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...

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 · Oct 14, 2026
Nº 01

Citations Did Not Stop Our AI From Inventing a Case Study

Our blog pipeline once wrote an invented case study: a draft about shipping BauGPT's WhatsApp bot in 10 days, with a cost breakdown in the headline. The...

6 MIN READ
↗
technical-deep-dive · Sep 21, 2026
Nº 02

What it took to ship BauGPT's Ausschreibungs-Radar

Most of the work in a tender radar is not the matching. It is everything around the matching that stops it from mailing the wrong company, at the wrong time...

6 MIN READ
↗
technical-deep-dive · Aug 31, 2026
Nº 03

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...

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.