BotID gegen AI-Budget-Abuse
Wie wir unseren öffentlichen AI-Chat vor automatisierten Budget-Drain-Angriffen schützen — ohne CAPTCHA und ohne UX-Reibung.
Jeder POST gegen unseren /api/chat kostet uns Geld — er löst eine billable streamText()-Call gegen das Vercel AI Gateway aus. Ohne Schutz reicht ein simples Bash-Script und ein Stündchen Wartezeit, um $50 Cloud-Budget zu verbrennen. Hier ist, wie wir das gelöst haben.
Das Problem
Unser AI-Chat-Widget steht öffentlich auf der Website. Der Use-Case ist klar: ein:e Besucher:in fragt „passt euer Pricing zu meinem Projekt?", das Modell antwortet mit Vorschlägen, das Lead kommt qualifiziert. Funktioniert.
Das Problem: der Endpoint ist öffentlich. Jede:r kann posten. Im Sommer hatten wir einen Test-Lauf mit einem Bot-Script, der 1000 Anfragen in 10 Minuten geschickt hat — Resultat: $8 AI-Cost in einer Stunde, alles Müll-Anfragen. Bei einer Dauerwelle wären das pro Tag dreistellig.
Was wir zuerst probiert haben
Rate Limits per IP
Klassisch: pro IP nur N Requests in T Sekunden. Funktioniert für naive Bots, scheitert bei distributed Botnets. Ein billiges Residential-Proxy-Subscription kostet $20/Monat und liefert tausende rotierende IPs. Die Ökonomie ist gegen uns.
Honeypot Field
Ein verstecktes Form-Feld, das echte User nie ausfüllen. Manche Scraper schon. Hilft gegen die Hälfte der naiven Bots, gegen Headless-Chrome aber gar nicht.
reCAPTCHA / hCaptcha
Funktioniert, aber: jede:r echte User muss klicken. UX-Drop messbar. Und für DSGVO-Compliance mit US-CAPTCHA-Anbietern ist Auftragsverarbeitung notwendig. Wollten wir nicht.
Was wir gemacht haben: Vercel BotID
BotID ist Vercels Bot-Detection-Service, powered by Kasada. Drei Eigenschaften, die für uns entschieden haben:
- Invisible: kein CAPTCHA, kein „I'm not a robot"-Klick. Erkennt anhand von Browser-Fingerprint + Interaction-Signals (Mouse-Movement-Pattern, Keyboard-Timing).
- Dynamic: Detection-Methoden rotieren pro Page-Load. Macht Reverse-Engineering durch Angreifer extrem aufwendig.
- EU-konform: Vercel als Auftragsverarbeiter mit AVV-Coverage.
Integration in 4 Schritten
1. Package installieren:
npm install botid
2. next.config.ts wrappen — das wired Middleware-Signals:
import { withBotId } from "botid/next/config";
const nextConfig: NextConfig = { /* … */ };
export default withBotId(nextConfig);
3. <BotIdClient> ins app/layout.tsx einbauen — das sammelt Client-Signals und attached sie an Fetches gegen die geschützten Routen:
import { BotIdClient } from "botid/client";
const PROTECT = [
{ path: "/api/chat", method: "POST" as const },
{ path: "/api/contact", method: "POST" as const },
];
export default function Layout({ children }) {
return (
<html>
<body>
<BotIdClient protect={PROTECT} />
{children}
</body>
</html>
);
}
4. Im Route-Handler checkBotId() aufrufen, vor jeglichem teuren Call:
import { checkBotId } from "botid/server";
export async function POST(req: Request) {
const bot = await checkBotId();
if (bot.isBot && !bot.bypassed) {
return Response.json(
{ error: "rate_limited" },
{ status: 429 }
);
}
// teurer streamText() Call läuft nur für Menschen
const result = streamText({ model: "openai/gpt-5-mini", ... });
return result.toUIMessageStreamResponse();
}
Wichtig: das richtige Verhalten bei Bot-Detection
Wir geben Bots 200 OK mit benigner JSON-Antwort oder 429 — nie 401/403. Wenn der Bot weiß dass er erkannt wurde, optimiert er. Wenn er denkt er kommt durch, ist's ihm schlicht egal — er bekommt nur keinen AI-Output.
Pretend success. Lass den Bot in den Sand laufen, statt ihm zu sagen warum.
Was wir gelernt haben
Server-only Detection ist schwach
Wer nur checkBotId() aufruft ohne <BotIdClient> einzubauen, kriegt nur UA + Header-basierte Detection. Headless-Chrome mit gespoofter UA kommt da locker durch. Erst der Client-Layer macht's robust.
Listen geschützter Routen einmal pflegen
Zentral in app/layout.tsx. Wenn ein neuer expensive Endpoint dazukommt, in beiden Stellen ergänzen: BotIdClient.protect[] + checkBotId()-Call im Route-Handler.
Test mit echtem cURL
Nach Deploy: curl -X POST https://deine-domain.com/api/chat -d '{...}' — sollte 429 zurückgeben (kein Browser-Signal vorhanden). Wenn da 200 mit AI-Antwort kommt, ist die Detection nicht aktiv.
Was es uns gekostet hat
BotID ist bei Vercel im Pro-Plan inkludiert (Deep-Analysis kostet extra für sehr hohe Volumes). Setup-Zeit: 20 Minuten. Wartung: null. Kein eigener Anti-Abuse-Code, kein CAPTCHA-Service, kein User-Friction.
Ergebnis nach 4 Wochen: AI-Gateway-Spend pro Tag stabil unter $1, vorher war Spike-Range $5-$15 abhängig vom Bot-Traffic. Auf Jahressicht spart das ~$3000 — und wichtiger: macht den Endpoint dauerhaft verteidigbar.
Wenn dein API-Endpoint pro Call mehr kostet als 0,1¢ und öffentlich ist, brauchst du Bot-Detection. Heute. Nicht nächste Woche.
Neue Insights direkt im Postfach
Ehrliche Notes aus Engineering, AI und echten Cases. Etwa 1× im Monat, jederzeit abbestellbar.
Hilft das deinem Vorhaben weiter?
Lass uns 30 Min sprechen — konkret zu deinem Case.
Auch lesenswert
Wie wir aus einem Chat-Widget einen Conversion-Funnel gemacht haben
Ein Floating-Chat-Widget allein konvertiert nicht. Das mussten wir lernen — und sechs Pattern nachbauen, damit aus klick-scroll-schließ ein messbarer Funnel von Open bis Erstgespräch wurde.
Wenn der eigene Bot-Schutz dich aussperrt
Wir haben heute BotID auf /api/chat aktiviert. Erste echte Lektion kam nicht von einem Angreifer — sondern von unserem eigenen Verifikations-Script, das prompt 403'te. Was wir daraus mitgenommen haben.
Kein Funnel, kein Sales-Rep. Du redest mit mir.
Ich höre dir 30 Minuten zu, stelle ein paar gezielte Fragen und sage dir am Ende ehrlich, ob — und wie — wir dir helfen können. Wenn nicht, bekommst du mindestens zwei Empfehlungen, wer's könnte.
- 30 Min, kein Sales-Pitch
- Konkrete Einschätzung deines Cases
- Fixpreis-Indikation am Ende des Calls