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 →
build-in-public · Jun 1, 2026
$ we run ai agents inside baugpt.
# build-in-public
read-time: 7min

We run AI agents inside BauGPT. Here's what it taught us about building them.

We build AI for the construction industry. We also run AI agents inside our own company to handle scheduling, ticket routing, code review, and content ops. That...

7 MIN READ
product · May 21, 2026
02

Our enterprise onboarding takes 90 minutes. The procurement took 11 weeks.

A construction company with a four-billion-euro annual turnover signed up for BauGPT last quarter. Their procurement process took eleven weeks. The actual onboa...

4 MIN READ
product · May 21, 2026
03

We process 40,000 WhatsApp messages a week. Here's why we built there.

BauGPT processes 40,000 WhatsApp messages a week. About 40% of them are voice notes. I mention this not to flex on a number. I mention it because it explains ev...

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