# So fügst du ein Honeypot-Feld hinzu, das Bots ausfüllen und Menschen nicht

> Füge jedem Formular einen Honeypot hinzu, der Bots abfängt, ohne Screenreader, Tastatur oder Autofill zu stören, mit der Serverprüfung für jeden Stack.

Veröffentlicht am 2026-10-25 · https://ikiform.com/de/guides/spam-protection/honeypot

Füge ein zusätzliches Texteingabefeld hinzu, das du außerhalb des sichtbaren Bereichs versteckst, aus der Tab-Reihenfolge nimmst und in `aria-hidden` einpackst, mit einem Namen, den kein Browser automatisch ausfüllt. Menschen füllen es nie aus; Bots, die jedes Eingabefeld ausfüllen, schon. Behandle auf dem Server jeden Post mit einem Wert darin als Spam und antworte genauso wie auf eine echte Nachricht.

## Was an einem Honeypot besonders ist

Ein Honeypot muss zwei Dinge zugleich sein. Für einen Bot, der das HTML liest, muss er wie ein normales Feld aussehen, das sich auszufüllen lohnt. Für jeden Menschen darf er nicht existieren, auch für Leute, die das Formular anders nutzen als du: jemand mit Screenreader, jemand, der mit der Tastatur tabbt, jemand, dessen Passwortmanager Formulare ausfüllt. Die meisten Honeypot-Fehler entstehen, weil eine dieser Personen vergessen wurde, und das Symptom ist eine echte Nachricht, die still als Spam abgelegt wird.

Diagramm: Wie ein Mensch und ein Bot auf dasselbe Honeypot-Feld treffen

1. Ein Mensch füllt das Formular mit dem aus, was er nutzt: Bildschirm, Tastatur oder Screenreader.
2. Die Falle liegt außerhalb des sichtbaren Bereichs, außerhalb der Tab-Reihenfolge und ist vor Hilfstechnologien verborgen, also erreicht er sie nie.
3. Ihr Name ist einer, den Browser und Passwortmanager nicht automatisch ausfüllen, also bleibt sie leer.
4. Der Post kommt mit leerer Falle an und wird angenommen.
5. Ein Skript-Bot parst das HTML und findet jedes Eingabefeld, auch die Falle.
6. Er füllt alle aus und sendet sofort.
7. Die Falle hat einen Wert, also ist der Post Spam. Der Bot bekommt trotzdem eine normale Erfolgsantwort.

Jede Schutzmaßnahme in der oberen Reihe schließt einen Weg, auf dem ein echter Mensch das Feld erreichen könnte. Lass eine weg, und auch die obere Reihe kann im Spam enden.

## Das Markup

Setze die Falle ins Formular, neben die echten Felder, als einfaches Texteingabefeld. Ein Eingabefeld mit `type="hidden"` funktioniert nicht: Bots wissen, dass Menschen solche Felder nicht bearbeiten können, und lassen sie in Ruhe.

`contact.html`:

```html
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="POST">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />
  <label for="message">Message</label>
  <textarea id="message" name="message" required></textarea>

  <div class="hp" aria-hidden="true">
    <label for="hp-field">Leave this empty</label>
    <input id="hp-field" name="_gotcha" type="text" tabindex="-1" autocomplete="off" />
  </div>

  <button>Send</button>
</form>
```

`Styles`:

```html
<style>
.hp {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
</style>
```

- `aria-hidden="true"` am Wrapper entfernt Label und Eingabefeld aus dem Accessibility Tree, sodass Screenreader sie nicht vorlesen.
- `tabindex="-1"` verhindert, dass die Tab-Taste auf einem Eingabefeld landet, das niemand sehen kann.
- Das Label lautet **Leave this empty**. Falls ein Tool das Feld trotzdem anzeigt, weiß die Person, die es liest, was zu tun ist.
- Füge kein `required` hinzu. Ein Pflichtfeld, das Menschen nicht sehen können, blockiert jedes ehrliche Absenden.

## Wie du ihn versteckst

Bots haben gelernt, offensichtlich versteckte Felder zu überspringen. Einfache Scraper füllen immer noch alles aus, aber manche prüfen das `hidden`-Attribut oder ein Inline-`display: none`, bevor sie tippen. Je weiter das Verstecken vom Eingabefeld selbst entfernt ist, desto mehr Bots fallen darauf herein.

| Technik | Vor Menschen verborgen | Bots, die sie noch fängt |
| --- | --- | --- |
| `hidden`-Attribut oder Inline-`display: none` | Ja | Nur die einfachsten. Das prüft ein schlauerer Bot als Erstes |
| Off-Screen-Klasse in deinem Stylesheet | Ja | Die meisten. Der Bot müsste dein CSS laden und anwenden, um es zu merken |
| `opacity: 0` oder Größe null | Meistens. Manche Bildschirmlupen und Fokusrahmen zeigen es trotzdem | Die meisten, aber riskanter für Menschen |
| Per JavaScript nach dem Laden hinzugefügt | Ja | Bots, die Skripte ausführen. Einfache HTML-Scraper sehen es nie, verpassen es also |

Verwende die Off-Screen-Klasse aus deinem Stylesheet. Sie fängt die meisten Bots und ist für Menschen am sichersten. Wenn ein Honeypot nicht mehr wirkt, füge einen zweiten mit anderem Namen hinzu, statt einen raffinierteren Versteck-Trick zu suchen.

## Wähle einen Namen, den Autofill ignoriert

Browser und Passwortmanager füllen Felder, indem sie anhand von `name`, `id`, Label und `autocomplete`-Wert raten. `autocomplete="off"` ist nur ein Hinweis, und manche Browser ignorieren ihn bei Feldern, die nach Adressen oder Kontaktdaten aussehen. Die eigentliche Arbeit leistet also der Name.

| Vermeiden | Warum | Stattdessen |
| --- | --- | --- |
| `email2`, `phone`, `name`, `address` | Autofill erkennt sie und füllt sie für echte Menschen aus | `_gotcha`, `company_website` |
| `honeypot`, `trap`, `bot` | Manche Bots überspringen Felder mit offensichtlichen Namen | Ein langweiliger, plausibler Name |
| `url`, `website` allein | Manche Autofill-Tools füllen ein Website-Feld aus einem gespeicherten Profil | Ein zusammengesetzter Name wie `company_website` |

## Auf dem Server prüfen

Ein Honeypot wirkt nur, wenn der Server ihn liest. Eine Prüfung im Browser ist nutzlos, da ein Bot dein Skript nie ausführt. Hat das Feld einen Wert, antworte genauso wie auf eine echte Nachricht und speichere oder leite den Post nicht weiter. Eine andere Antwort verrät dem Bot, welches Feld ihn entlarvt hat. Die Beispiele unten rufen dein eigenes `saveMessage` auf; ersetze es durch das, was die Nachricht speichert oder mailt.

**HTML**

Ein einfaches HTML-Formular hat keinen eigenen Server. Richte es auf Ikiform aus und nenne die Falle `_gotcha`, oder benenne sie pro Formular unter **Honeypot field** um. Ikiform macht die Serverprüfung, und die Falle wird nie als Antwort gespeichert.

`contact.html`:

```html
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="POST">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />
  <label for="message">Message</label>
  <textarea id="message" name="message" required></textarea>

  <div class="hp" aria-hidden="true">
    <label for="hp-field">Leave this empty</label>
    <input id="hp-field" name="_gotcha" type="text" tabindex="-1" autocomplete="off" />
  </div>

  <button>Send</button>
</form>
```

**Next.js**

Leite in einer Server Action einen in die Falle gegangenen Post auf dieselbe Dankeseite weiter wie einen echten und überspringe das Speichern.

`app/contact/actions.ts`:

```ts
"use server";

import { redirect } from "next/navigation";

export async function sendMessage(formData: FormData) {
  if (String(formData.get("company_website") ?? "").trim() !== "") {
    console.info("honeypot", { at: new Date().toISOString() });
    redirect("/contact/thanks");
  }
  await saveMessage({
    email: String(formData.get("email") ?? ""),
    message: String(formData.get("message") ?? ""),
  });
  redirect("/contact/thanks");
}
```

**Astro**

Nimm in einer Astro Action die Falle aus der Eingabe und gib das normale Ergebnis zurück, wenn sie einen Wert hat.

`src/actions/index.ts`:

```ts
import { defineAction } from "astro:actions";
import { z } from "astro/zod";

export const server = {
  sendMessage: defineAction({
    accept: "form",
    input: z.object({
      email: z.email(),
      message: z.string().min(1),
      company_website: z.string().nullish(),
    }),
    handler: async ({ company_website, ...fields }) => {
      if (company_website?.trim()) return { sent: true };
      await saveMessage(fields);
      return { sent: true };
    },
  }),
};
```

**TanStack Start**

Lies die Falle in `.validator()` und überspringe das Speichern im Handler. Beide Wege geben dieselbe Antwort zurück.

`src/server/send-message.ts`:

```ts
import { createServerFn } from "@tanstack/react-start";

export const sendMessage = createServerFn({ method: "POST" })
  .validator((data: unknown) => {
    if (!(data instanceof FormData)) throw new Error("Expected form data");
    return {
      email: String(data.get("email") ?? ""),
      message: String(data.get("message") ?? ""),
      trapped: String(data.get("company_website") ?? "").trim() !== "",
    };
  })
  .handler(async ({ data: { trapped, ...fields } }) => {
    if (!trapped) await saveMessage(fields);
    return { ok: true };
  });
```

**React Router**

Die Route-Action leitet in beiden Fällen auf die Dankeseite weiter und speichert nur einen Post, der nicht in die Falle gegangen ist.

`app/routes/contact.tsx`:

```ts
import { redirect } from "react-router";
import type { Route } from "./+types/contact";

export async function action({ request }: Route.ActionArgs) {
  const form = await request.formData();
  if (String(form.get("company_website") ?? "").trim()) return redirect("/contact/thanks");
  await saveMessage({ email: String(form.get("email")), message: String(form.get("message")) });
  return redirect("/contact/thanks");
}
```

**SvelteKit**

Die Form-Action speichert nur, wenn die Falle leer ist, und leitet dann in beiden Fällen mit `303` weiter.

`src/routes/contact/+page.server.ts`:

```ts
import { redirect } from "@sveltejs/kit";
import type { Actions } from "./$types";

export const actions = {
  default: async ({ request }) => {
    const form = await request.formData();
    if (!String(form.get("company_website") ?? "").trim()) {
      await saveMessage({ email: String(form.get("email")), message: String(form.get("message")) });
    }
    redirect(303, "/contact/thanks");
  },
} satisfies Actions;
```

**Nuxt**

Die Server-Route antwortet auf einen Post in der Falle und einen echten mit `201` und demselben Body.

`server/api/contact.post.ts`:

```ts
export default defineEventHandler(async (event) => {
  const body = await readBody<{ email?: string; message?: string; company_website?: string }>(event);
  if (body.company_website?.trim()) {
    setResponseStatus(event, 201);
    return { sent: true };
  }
  await saveMessage({ email: body.email ?? "", message: body.message ?? "" });
  setResponseStatus(event, 201);
  return { sent: true };
});
```

**React**

Eine Single-Page-App kann die Falle nicht selbst prüfen. Sende sie mit den anderen Feldern und lass den Server, an den du sendest, entscheiden. `FormData` schließt sie ohne zusätzlichen Code ein.

`submit.ts`:

```ts
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
  method: "POST",
  headers: { Accept: "application/json" },
  body: new FormData(form),
});
```

**Vue**

Genauso in Vue: Behalte die Falle im `<form>`, damit `new FormData(form)` sie einschließt, und lass den Server sie prüfen.

`submit.ts`:

```ts
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
  method: "POST",
  headers: { Accept: "application/json" },
  body: new FormData(form),
});
```

> Logge Posts, die in die Falle gehen, auch wenn du sie nicht speicherst. Eine Zahl pro Tag sagt dir, ob sich der Honeypot noch lohnt, und ein plötzlicher Abfall auf null bedeutet, dass Bots gelernt haben, ihn zu überspringen.

## Wie Ikiform mit dem Honeypot umgeht

Jedes Ikiform-Formular hat standardmäßig einen Honeypot namens `_gotcha`. Ändere ihn pro Formular unter **Honeypot field** in den Einstellungen des Formulars; jeder Name, den du wählst, ist reserviert und wird nie als Antwort gespeichert. Ein ausgefüllter Honeypot erhöht den Spam-Score um 5, was allein die Schwelle von 5 erreicht.

Ikiform verwirft den Post nicht. Es behält ihn als Spam, sodass du die Nachricht finden und wiederherstellen kannst, falls je ein echter Mensch in die Falle tappt. Ein als Spam eingestufter Post bekommt dieselbe Erfolgsantwort wie ein echter, ein Bot lernt also nichts. Er wird mit dem Status `spam` gespeichert, löst weder Benachrichtigungs-E-Mail noch Webhook aus, zählt nicht zu deiner Nutzung und wird nach 30 Tagen gelöscht. Jeden Montag bekommt ein Workspace, der Spam blockiert hat, eine Zusammenfassung per E-Mail mit der Gesamtzahl, den meistbetroffenen Formularen und den häufigsten Gründen. Die [Spam-Dokumentation](https://ikiform.com/de/docs/spam.md#honeypot) und [HTML-Formulare](https://ikiform.com/de/docs/html.md#honeypot) enthalten die Referenz.

## Prüfen, ob es funktioniert

1. Lade das Formular mit eingeschaltetem Screenreader und geh es durch. Die Falle wird nie vorgelesen.
2. Tabbe durch das Formular. Der Fokus springt vom letzten echten Feld direkt zum Button.
3. Lass Browser und Passwortmanager das Formular automatisch ausfüllen. Die Falle bleibt leer.
4. Sende einen Post mit ausgefülltem Honeypot, `_gotcha=bot`. Er bekommt trotzdem eine Erfolgsantwort, aber die Einsendung wird als Spam gespeichert und löst weder E-Mail noch Webhook aus.
5. Sende eine normale Nachricht. Sie kommt als angenommen an, ohne Spur des Fallen-Felds.

## Stolperfallen

- **JSON-Posts.** Wenn du den Body von Hand statt mit `FormData` baust, nimm die Falle mit auf, sonst liest dein Server sie auch bei Bots als leer.
- **Formular-Builder, die Felder umbenennen.** Manche Builder versehen Eingabefelder mit Präfixen oder benennen sie um. Prüfe im gesendeten Body, nicht im Editor, wie die Falle wirklich heißt.
- **Caching.** Hat eine gecachte Seite nach dem Umbenennen noch den alten Fallennamen, kommen Posts mit dem alten Namen an und gehen durch. Benenne auf beiden Seiten gleichzeitig um.
- **Checkbox-Honeypots.** Eine versteckte Checkbox funktioniert auch, aber Screenreader gehen mit verirrten Checkboxen schlechter um als mit Texteingabefeldern. Nimm lieber ein Texteingabefeld.
- **Eine Schicht.** Bots, die einen echten Browser ausführen und unsichtbare Felder überspringen, kommen an jedem Honeypot vorbei. Kombiniere ihn mit einem [Rate-Limit](https://ikiform.com/de/guides/spam-protection/rate-limiting.md) und bei Bedarf einer [Menschlichkeitsprüfung](https://ikiform.com/de/guides/spam-protection/cloudflare-turnstile.md).

## Fragen

### Funktionieren Honeypot-Felder noch gegen Spam-Bots?

Ja, gegen die häufigsten: Skripte, die jedes Eingabefeld ausfüllen, das sie finden. Bots, die einen vollständigen Browser ausführen und unsichtbare Felder überspringen, kommen durch, also nutze einen Honeypot als erste Schicht, nicht als einzige.

### Ist ein Honeypot-Feld schlecht für die Barrierefreiheit?

Nur, wenn es nachlässig versteckt ist. Pack es in ein Element mit aria-hidden, gib dem Eingabefeld tabindex -1, beschrifte es mit Leave this empty und markiere es nie als Pflicht. Screenreader und Tastatur überspringen es dann.

### Warum ist eine echte Nachricht in meinem Honeypot hängen geblieben?

Meist wegen Autofill. Ein Browser oder Passwortmanager hat die Falle ausgefüllt, weil ihr Name wie ein echtes Feld aussah, etwa email2 oder phone. Benenne sie in etwas um, das kein Autofill erkennt.

### Sollte ich dem Absender sagen, dass seine Nachricht abgelehnt wurde?

Nein. Beantworte einen Post in der Falle genauso wie einen echten. Ein Fehler verrät dem Bot, welches Feld ihn erwischt hat, und die nächste Version des Bots überspringt dieses Feld.

## Weitere Anleitungen

- [Spam im Kontaktformular stoppen](https://ikiform.com/de/guides/spam-protection.md): Stopp Kontaktformular-Spam mit Schichten, die echte Menschen nichts kosten: Honeypot, Timing, Inhaltsbewertung, Rate-Limits und eine Menschlichkeitsprüfung nur bei Bedarf.
- [Ohne Captcha](https://ikiform.com/de/guides/spam-protection/without-captcha.md): Stopp den meisten Formular-Spam ganz ohne Captcha: Honeypot, signierte Render-Zeit, Inhaltsbewertung und Rate-Limit, zusammen statt einzeln eingesetzt.
- [Rate-Limits](https://ikiform.com/de/guides/spam-protection/rate-limiting.md): Begrenze Formulareinsendungen pro Besucher mit einem gemeinsamen Speicher, antworte mit 429 und Retry-After und sperre keine Büros aus, die sich eine IP teilen.
- [Cloudflare Turnstile](https://ikiform.com/de/guides/spam-protection/cloudflare-turnstile.md): Füge Cloudflare Turnstile zu einem einfachen HTML-Formular hinzu: das Widget, serverseitiges siteverify, Testschlüssel und ein Weg ganz ohne eigene Schlüssel oder Server.
- [Alternativen zu reCAPTCHA](https://ikiform.com/de/guides/spam-protection/recaptcha-alternatives.md): Vergleiche reCAPTCHA-Alternativen für Formulare, von Turnstile und hCaptcha bis Proof-of-Work und ganz ohne Captcha, und wechsle mit einer kleinen, sicheren Änderung.
- [Statische Sites](https://ikiform.com/de/guides/spam-protection/static-site.md): Stopp Spam in einem Formular einer statischen Site, obwohl sie kein Geheimnis verwahren und keine Posts zählen kann, mit einem Honeypot-Partial, einem sicheren Skript und serverseitigen Prüfungen.
