Catatan dari produksi
FormsWeb3FormsServerless

Cara kerja form Adopt-Assessment di /book

Halaman /book punya satu form lead — Adopt Assessment. Tapi situs ini statis (SSG): tiap halaman adalah HTML yang sudah jadi, disajikan dari CDN, tanpa server kami sendiri. Jadi ke mana submit-nya pergi? Berikut cara kerjanya, sampai ke detailnya.

Masalahnya: situs statis tak punya server

Situs ini di-prerender saat build lalu disajikan sebagai file statis dari edge Cloudflare. Cepat dan murah — tapi artinya tak ada endpoint backend yang bisa menerima POST dari form. Cara klasik (form mengirim ke skrip server) tak tersedia. Kami butuh tempat lain untuk menampung submission.

Solusinya: Web3Forms (form-backend sebagai layanan)

Form-nya POST langsung dari browser ke Web3Forms, sebuah layanan yang menerima submission lalu meneruskannya ke inbox Anda. Tak ada server kami di tengah — hanya fetch dari sisi klien:

const res = await fetch('https://api.web3forms.com/submit', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
  body: JSON.stringify({
    access_key: WEB3FORMS_KEY, // publik — hanya merutekan ke inbox, tak membaca apa pun
    name, email, phone, company, about,
    botcheck: false,           // honeypot (lihat di bawah)
    ...attribution,            // utm_*, referrer, landing_page — tanpa cookie
  }),
})
const data = await res.json()
if (data.success) {
  // tampilkan "terkirim", reset form, kirim event generate_lead
}
1 · form di /bookHTML statis dari CDN — tanpa server kamifetch · POST JSON2 · api.web3forms.commenerima submission — bukan backend kamicek honeypot + key3 · email ke inbox Andasatu lead = satu email4 · suksesGA4 generate_lead (cookieless) · form: "terkirim"tak ada server kami di alur ini — hanya browser → Web3Forms
Submission tak pernah menyentuh server kami: browser mem-POST langsung ke Web3Forms, yang mengirim email lalu mengembalikan JSON sukses — saat itulah kami catat sinyal konversinya.

Access key-nya publik — dan itu memang aman

access_key-nya di-commit ke repo, terlihat di bundle. Itu bukan kebocoran: access key Web3Forms cuma merutekan submission ke inbox yang dikonfigurasi. Ia tak bisa membaca data, tak memberi akses ke akun, tak menandatangani apa pun — paling jauh, orang lain bisa mengirim submission ke inbox yang sama (dan untuk itu ada honeypot + rate limit Web3Forms; kalau disalahgunakan, tinggal rotasi key-nya). Ini perbedaan penting: bukan tiap string panjang itu rahasia. Yang rahasia adalah yang bisa membaca atau mengubah sesuatu.

Anti-spam: honeypot

Tanpa server, kami tak bisa menjalankan cek khusus — jadi form memasang honeypot: satu field tersembunyi (botcheck) yang diposisikan di luar layar dan disembunyikan dari screen reader. Manusia tak pernah melihatnya, jadi tak pernah mengisinya; banyak bot mengisi tiap field yang ditemukan. Web3Forms menolak submission yang field honeypot-nya terisi.

{/* honeypot — manusia tak melihatnya; bot yang mengisi → ditolak */}
<input type="text" name="botcheck" tabIndex={-1} aria-hidden="true"
  style={{ position: 'absolute', left: '-9999px', opacity: 0 }} />

Atribusi tanpa cookie

Saat pengunjung pertama mendarat, form menyimpan first-touch attributionutm_*, referrer, dan landing page — ke sessionStorage, lalu menempelkannya ke tiap submission. Ini berjalan tanpa cookie dan tanpa persetujuan: sessionStorage bukan cookie, hanya hidup selama tab, dan tak melacak Anda lintas situs. Jadi lead datang dengan sumbernya melekat — dari mana kampanye, dari referrer mana — tanpa menyentuh consent.

Sinyal konversi: generate_lead

Saat Web3Forms mengembalikan success, form mengirim event GA4 generate_lead (lewat gtag) membawa utm_* first-touch. Kalau VITE_GA_ID tak diset, track() jadi no-op — jadi tanpa GA pun form tetap bekerja. Dan di bawah Consent Mode v2 'denied', GA4 tetap mengirim event itu cookieless (tanpa client-id), jadi kami menghormati consent tanpa kehilangan sinyal agregat.

State & aksesibilitas

Form punya empat state — idle · sending · sent · error. Tombol berganti label dan dinonaktifkan saat sending (mencegah double-submit). Hasilnya diumumkan ke teknologi bantu: sukses pakai role="status", error dan error validasi telepon pakai role="alert". Tiap field punya <label htmlFor>, dan input telepon mengikat error-nya lewat aria-invalid + aria-describedby. Validasi nomor dilakukan di klien (^\d{6,15}$) sebelum apa pun terkirim.

CSP: kenapa fetch, bukan action form

Situs ini mengunci Content-Security-Policy tanpa 'unsafe-inline'. Dua arahan menentukan ke mana form boleh bicara:

Pertanyaan umum

Apakah access key Web3Forms itu rahasia?

Bukan — ia publik secara desain dan aman untuk di-commit. Access key cuma merutekan submission ke inbox yang dikonfigurasi; ia tak bisa membaca data, mengakses akun, atau menandatangani apa pun. Paling jauh orang lain bisa mengirim submission ke inbox yang sama — yang ditangani honeypot plus rate limit Web3Forms, dan key-nya bisa dirotasi kalau disalahgunakan.

Bagaimana spam ditangani tanpa CAPTCHA?

Dengan honeypot: satu field tersembunyi (botcheck) yang manusia tak pernah lihat atau isi, tapi banyak bot mengisinya secara otomatis. Submission yang field-nya terisi ditolak. Web3Forms juga menjalankan cek spam dan rate limit di sisi server. Untuk volume lebih tinggi, CAPTCHA tanpa-friksi seperti Cloudflare Turnstile bisa ditambahkan.

Apakah pelacakan UTM/referrer butuh persetujuan cookie?

Tidak. Atribusi disimpan di sessionStorage (bukan cookie, hanya hidup selama tab, tak lintas situs) lalu ditempelkan ke submission — jadi tak butuh consent. Event generate_lead GA4 juga dikirim cookieless di bawah Consent Mode 'denied', jadi sinyal konversi tetap menghormati pilihan pengguna.

Tempatnya di mana

Pola ini bersandar pada hosting statis dari CI/CD: GitHub Actions → Cloudflare Pages dan baseline keamanan dari SSL & keamanan di edge (CSP-nya mengizinkan connect-src ini). Form-nya sendiri hidup di /book.

Sources

  1. Web3Forms — dokumentasi
  2. MDN — Fetch API
  3. MDN — CSP connect-src
  4. GA4 — generate_lead