# Cara menambahkan field honeypot yang diisi bot tetapi tidak oleh manusia

> Tambahkan honeypot ke formulir apa pun yang menangkap bot tanpa mengganggu screen reader, keyboard, atau autofill, dengan pemeriksaan server untuk setiap stack.

Terbit 2026-10-25 · https://ikiform.com/id/guides/spam-protection/honeypot

Tambahkan satu input teks ekstra yang Anda sembunyikan di luar layar, keluarkan dari urutan tab, dan bungkus dengan `aria-hidden`, dengan nama yang tidak diisi otomatis oleh browser mana pun. Manusia tidak pernah mengisinya; bot yang mengisi setiap input akan mengisinya. Di server, anggap setiap kiriman yang field itu berisi nilai sebagai spam, dan jawab persis seperti untuk pesan asli.

## Apa yang berbeda dari honeypot

Honeypot harus menjadi dua hal sekaligus. Bagi bot yang membaca HTML, ia harus terlihat seperti field biasa yang layak diisi. Bagi setiap orang, ia harus tidak ada, termasuk orang yang memakai formulir dengan cara berbeda dari Anda: pengguna screen reader, orang yang menavigasi dengan keyboard, orang yang formulirnya diisi oleh password manager. Sebagian besar bug honeypot datang dari melupakan salah satu orang ini, dan gejalanya adalah pesan asli yang diam-diam masuk ke spam.

Diagram: Bagaimana manusia dan bot bertemu field honeypot yang sama

1. Seseorang mengisi formulir dengan apa pun yang ia pakai: layar, keyboard, atau screen reader.
2. Jebakan ada di luar layar, di luar urutan tab, dan tersembunyi dari teknologi bantu, jadi ia tidak pernah mencapainya.
3. Namanya tidak diisi otomatis oleh browser dan password manager, jadi tetap kosong.
4. Kiriman tiba dengan jebakan kosong dan diterima.
5. Bot skrip mem-parse HTML dan menemukan setiap input, termasuk jebakannya.
6. Bot mengisi semuanya dan langsung mengirim.
7. Jebakan berisi nilai, jadi kiriman dianggap spam. Bot tetap mendapat jawaban sukses biasa.

Setiap pengaman di baris atas menutup satu jalan bagi orang sungguhan untuk mencapai field itu. Hilangkan salah satunya dan baris atas pun bisa berakhir di spam.

## Markup-nya

Taruh jebakan di dalam formulir, di samping field asli, sebagai input teks biasa. Input `type="hidden"` tidak berhasil: bot tahu orang tidak bisa mengeditnya, dan membiarkannya.

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

`gaya`:

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

- `aria-hidden="true"` pada wrapper menghapus label dan input dari accessibility tree, sehingga screen reader tidak membacakannya.
- `tabindex="-1"` mencegah tombol Tab berhenti di input yang tidak terlihat siapa pun.
- Labelnya berbunyi **Leave this empty** (biarkan kosong). Jika ada alat yang masih menampilkan field itu, orang yang membacanya tahu apa yang harus dilakukan.
- Jangan tambahkan `required`. Field wajib yang tidak bisa dilihat orang memblokir setiap kiriman yang jujur.

## Cara menyembunyikannya

Bot sudah belajar melewati field yang jelas-jelas tersembunyi. Scraper sederhana masih mengisi semuanya, tetapi sebagian memeriksa atribut `hidden` atau `display: none` inline sebelum mengetik. Semakin jauh penyembunyiannya dari input itu sendiri, semakin banyak bot yang terjebak.

| Teknik | Tersembunyi dari manusia | Bot yang masih tertangkap |
| --- | --- | --- |
| Atribut `hidden` atau `display: none` inline | Ya | Hanya yang paling sederhana. Ini hal pertama yang diperiksa bot yang lebih pintar |
| Class di luar layar di stylesheet Anda | Ya | Sebagian besar. Bot harus memuat dan menerapkan CSS Anda untuk mengetahuinya |
| `opacity: 0` atau ukuran nol | Sebagian besar. Beberapa pembesar layar dan outline fokus masih menampakkannya | Sebagian besar, tetapi lebih berisiko bagi manusia |
| Ditambahkan JavaScript setelah halaman dimuat | Ya | Bot yang menjalankan skrip. Scraper HTML biasa tidak pernah melihatnya, jadi mereka lolos |

Pakai class di luar layar dari stylesheet Anda. Cara ini menangkap bot paling banyak dan paling aman bagi manusia. Jika satu honeypot berhenti bekerja, tambahkan yang kedua dengan nama berbeda, alih-alih trik penyembunyian yang lebih cerdik.

## Pilih nama yang diabaikan autofill

Browser dan password manager mengisi field dengan menebak dari `name`, `id`, label, dan nilai `autocomplete`-nya. `autocomplete="off"` hanyalah petunjuk, dan sebagian browser mengabaikannya untuk field yang terlihat seperti alamat atau detail kontak. Jadi nama yang sebenarnya berperan.

| Hindari | Alasan | Gunakan sebagai gantinya |
| --- | --- | --- |
| `email2`, `phone`, `name`, `address` | Autofill mengenalinya dan mengisinya untuk orang sungguhan | `_gotcha`, `company_website` |
| `honeypot`, `trap`, `bot` | Sebagian bot melewati field dengan nama yang mencolok | Nama yang membosankan tetapi masuk akal |
| `url`, `website` saja | Sebagian alat autofill mengisi field website dari profil tersimpan | Nama gabungan seperti `company_website` |

## Periksa di server

Honeypot hanya berfungsi jika server membacanya. Pemeriksaan di sisi browser tidak berguna, karena bot tidak pernah menjalankan skrip Anda. Saat field berisi nilai, jawab persis seperti untuk pesan asli, dan jangan simpan atau teruskan kirimannya. Jawaban yang berbeda memberi tahu bot field mana yang membongkarnya. Contoh di bawah memanggil `saveMessage` milik Anda sendiri; ganti dengan apa pun yang menyimpan atau mengirim email pesan itu.

**HTML**

Formulir HTML biasa tidak punya server sendiri. Arahkan ke Ikiform dan beri nama jebakannya `_gotcha`, atau ganti namanya per formulir di **Honeypot field**. Ikiform melakukan pemeriksaan server, dan jebakan tidak pernah disimpan sebagai jawaban.

`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**

Di server action, redirect kiriman yang terjebak ke halaman terima kasih yang sama dengan kiriman asli, dan lewati penyimpanannya.

`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**

Di Astro Action, keluarkan jebakan dari input dan kembalikan hasil normal saat jebakan berisi nilai.

`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**

Baca jebakan di `.validator()` dan lewati penyimpanan di handler. Kedua jalur mengembalikan jawaban yang sama.

`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**

Route action me-redirect ke halaman terima kasih dalam kedua kasus dan hanya menyimpan kiriman yang tidak terjebak.

`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**

Form action hanya menyimpan saat jebakan kosong, lalu me-redirect dengan `303` untuk keduanya.

`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**

Server route menjawab `201` dengan body yang sama untuk kiriman yang terjebak maupun yang asli.

`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**

Single-page app tidak bisa memeriksa jebakan sendiri. Kirim bersama field lainnya, dan biarkan server tujuan yang memutuskan. `FormData` sudah menyertakannya tanpa kode tambahan.

`submit.ts`:

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

**Vue**

Sama di Vue: biarkan jebakan di dalam `<form>` agar `new FormData(form)` menyertakannya, dan biarkan server yang memeriksanya.

`submit.ts`:

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

> Catat kiriman yang terjebak, meskipun tidak Anda simpan. Hitungan per hari memberi tahu apakah honeypot masih berguna, dan penurunan mendadak ke nol berarti bot sudah belajar melewatinya.

## Cara Ikiform menangani honeypot

Setiap formulir Ikiform punya honeypot bernama `_gotcha` secara default. Ganti per formulir di **Honeypot field** pada pengaturan formulir; nama apa pun yang Anda pilih dicadangkan dan tidak pernah disimpan sebagai jawaban. Honeypot yang terisi menambah 5 ke skor spam, yang sudah mencapai ambang 5 dengan sendirinya.

Ikiform tidak membuang kirimannya. Kiriman disimpan sebagai spam, jadi jika suatu saat orang sungguhan terkena jebakan, Anda bisa menemukan pesannya dan memulihkannya. Kiriman yang dinilai spam mendapat jawaban sukses yang sama dengan kiriman asli, jadi bot tidak belajar apa-apa. Kiriman itu disimpan dengan status `spam`, tidak mengirim email notifikasi maupun webhook, tidak dihitung dalam pemakaian Anda, dan dihapus setelah 30 hari. Setiap Senin, workspace yang memblokir spam menerima satu email ringkasan berisi total, formulir tersibuk, dan alasan teratas. [Dokumentasi spam](https://ikiform.com/id/docs/spam.md#honeypot) dan [formulir HTML](https://ikiform.com/id/docs/html.md#honeypot) berisi referensinya.

## Pastikan berfungsi

1. Muat formulir dengan screen reader aktif dan telusuri. Jebakan tidak pernah dibacakan.
2. Tekan Tab di sepanjang formulir. Fokus berpindah dari field asli terakhir langsung ke tombol.
3. Biarkan browser dan password manager mengisi formulir otomatis. Jebakan tetap kosong.
4. Kirim dengan honeypot terisi, `_gotcha=bot`. Kiriman tetap mendapat jawaban sukses, tetapi disimpan sebagai spam dan tidak memicu email atau webhook.
5. Kirim pesan biasa. Pesan masuk sebagai diterima, tanpa jejak field jebakan.

## Hal yang sering menjebak

- **Kiriman JSON.** Jika Anda menyusun body secara manual alih-alih dengan `FormData`, sertakan jebakannya, atau server Anda akan membacanya kosong untuk bot juga.
- **Pembuat formulir yang mengganti nama field.** Sebagian pembuat situs memberi awalan atau mengganti nama input. Cek body yang dikirim, bukan editornya, untuk mengetahui nama asli jebakan.
- **Cache.** Jika halaman yang di-cache masih memakai nama jebakan lama setelah Anda menggantinya, kiriman datang dengan nama lama dan lolos. Ganti nama di kedua sisi sekaligus.
- **Honeypot checkbox.** Checkbox tersembunyi juga bisa, tetapi screen reader menangani checkbox nyasar lebih buruk daripada input teks. Utamakan input teks.
- **Hanya satu lapisan.** Bot yang menjalankan browser sungguhan dan melewati field tak terlihat bisa lolos dari honeypot apa pun. Pasangkan dengan [rate limit](https://ikiform.com/id/guides/spam-protection/rate-limiting.md) dan, jika perlu, [pemeriksaan manusia](https://ikiform.com/id/guides/spam-protection/cloudflare-turnstile.md).

## Pertanyaan

### Apakah field honeypot masih ampuh melawan bot spam?

Ya, melawan yang paling umum: skrip yang mengisi setiap input yang ditemukannya. Bot yang menjalankan browser penuh dan melewati field tak terlihat bisa lolos, jadi pakai honeypot sebagai lapisan pertama, bukan satu-satunya.

### Apakah field honeypot buruk untuk aksesibilitas?

Hanya jika disembunyikan sembarangan. Bungkus dalam elemen dengan aria-hidden, beri input tabindex -1, beri label Leave this empty, dan jangan pernah menandainya wajib. Screen reader dan keyboard pun akan melewatinya.

### Mengapa pesan asli tertangkap oleh honeypot saya?

Biasanya karena autofill. Browser atau password manager mengisi jebakan karena namanya terlihat seperti field asli, misalnya email2 atau phone. Ganti namanya menjadi sesuatu yang tidak dikenali autofill.

### Haruskah saya memberi tahu pengirim bahwa pesannya ditolak?

Tidak. Jawab kiriman yang terjebak persis seperti kiriman asli. Error memberi tahu bot field mana yang menangkapnya, dan versi bot berikutnya akan melewati field itu.

## Panduan lainnya

- [Hentikan spam formulir kontak](https://ikiform.com/id/guides/spam-protection.md): Hentikan spam formulir kontak dengan lapisan yang tidak membebani orang sungguhan: honeypot, waktu pengisian, skor konten, rate limit, dan pemeriksaan manusia hanya bila perlu.
- [Tanpa captcha](https://ikiform.com/id/guides/spam-protection/without-captcha.md): Hentikan sebagian besar spam formulir tanpa captcha sama sekali: honeypot, waktu render bertanda tangan, skor konten, dan rate limit, digabungkan alih-alih dipakai sendiri-sendiri.
- [Rate limiting](https://ikiform.com/id/guides/spam-protection/rate-limiting.md): Batasi rate kiriman formulir per pengunjung dengan store bersama, jawab 429 dengan Retry-After, dan hindari mengunci kantor yang berbagi satu IP.
- [Cloudflare Turnstile](https://ikiform.com/id/guides/spam-protection/cloudflare-turnstile.md): Tambahkan Cloudflare Turnstile ke formulir HTML biasa: widget, siteverify di sisi server, key uji, dan cara menjalankannya tanpa key atau server sendiri.
- [Alternatif reCAPTCHA](https://ikiform.com/id/guides/spam-protection/recaptcha-alternatives.md): Bandingkan alternatif reCAPTCHA untuk formulir, dari Turnstile dan hCaptcha hingga proof-of-work dan tanpa captcha sama sekali, lalu beralih dengan perubahan kecil yang aman.
- [Situs statis](https://ikiform.com/id/guides/spam-protection/static-site.md): Hentikan spam di formulir situs statis meskipun situs itu tidak bisa menyimpan secret atau menghitung kiriman, dengan partial honeypot, skrip yang aman, dan pemeriksaan sisi server.
