# Cara menambahkan daftar tunggu ke situs Framer

> Dua cara mengumpulkan pendaftaran daftar tunggu di Framer: formulir Framer dengan webhook ke Ikiform, atau code component yang mempertahankan batas per pengunjung dan pemeriksaan spam.

Terbit 2026-10-25 · https://ikiform.com/id/guides/waitlist/framer

Formulir Framer tidak bisa mengirim ke URL luar, tetapi bisa mengirim setiap kiriman ke webhook, jadi jalur tanpa kode adalah formulir Framer dengan endpoint Ikiform Anda sebagai webhook. Untuk peluncuran publik, gunakan code component kecil: browser mengirim langsung, jadi rate limit per pengunjung, domain yang diizinkan, dan honeypot dari Ikiform tetap berfungsi.

Banyak halaman pra-peluncuran dibangun di Framer, karena halamannya sendiri bisa selesai dalam satu sore. Formulir daftar tunggu adalah bagian yang kurang jelas. Blok formulir Framer tidak bisa mengubah `action`-nya, jadi Anda memilih antara dua jalur yang berperilaku berbeda begitu trafik sungguhan datang.

## Apa bedanya di Framer

- **Tidak ada action kustom.** Formulir Framer mengirim ke tujuan milik Framer: penyimpanannya sendiri, email, atau **Webhook**. Webhook adalah satu-satunya cara menjangkau backend luar tanpa kode.
- **Webhook datang dari server Framer, bukan dari pengunjung.** Ikiform melihat alamat IP Framer dan tidak ada header `Origin`. Itu mematikan tiga perlindungan: domain yang diizinkan, rate limit per pengunjung, dan pemeriksaan manusia.
- **Percobaan ulang.** Framer menganggap apa pun selain `2xx` sebagai kegagalan dan mencoba ulang hingga lima kali. Framer tidak mengikuti redirect. Ikiform menjawab kiriman JSON dengan `201`, jadi pendaftaran yang tersimpan langsung dianggap sukses sejak percobaan pertama.
- **Code component adalah React sungguhan.** Komponen ini berjalan di browser pengunjung di situs yang sudah dipublikasikan, jadi formulir di dalamnya mengirim seperti halaman web lain.

Diagram: Dua cara pendaftaran daftar tunggu Framer sampai ke Ikiform

1. Dengan jalur webhook, pengunjung mengirim formulir Framer dan server Framer meneruskan field-nya ke Ikiform sebagai JSON.
2. Ikiform melihat server Framer sebagai pengirim, jadi tidak bisa membedakan pengunjung atau memeriksa di situs mana formulir itu berada.
3. Dengan code component, browser pengunjung mengirim langsung ke Ikiform.
4. Ikiform melihat IP setiap pengunjung dan domain Anda, jadi setiap perlindungan berlaku.

Kedua jalur menyimpan pendaftaran yang sama. Bedanya ada pada siapa yang dianggap Ikiform sebagai pengirimnya.

## Pilih jalur

|   | Formulir Framer dengan webhook | Code component |
| --- | --- | --- |
| Setup | Dua field di sidebar kanan | Satu file kode, ditempel sekali |
| Gaya | Gaya dan state formulir Framer | Inline style yang Anda edit, atau class Anda sendiri |
| Rate limit per pengunjung | Tidak. Semua kiriman berbagi IP Framer | Ya, masing-masing 5 per menit |
| Domain yang diizinkan | Biarkan kosong | Isi dengan domain Framer dan domain kustom Anda |
| Honeypot | Tidak | Ya |
| Pemeriksaan manusia | Mati | Mati, karena mengirim dengan `fetch` |
| Pelacakan sumber | Tidak ada | `?ref=` dari URL, atau default per instance |

Jalur webhook sudah cukup untuk halaman yang Anda bagikan ke beberapa ratus orang. Jika Anda akan membagikan halaman di tempat besar, gunakan code component: lonjakan pendaftaran bot lewat server Framer hanya bisa dihentikan oleh penilaian spam.

## Jalur 1: formulir Framer dengan webhook

1. Tambahkan formulir ke halaman dengan satu input **Email**. Di pengaturan input, atur namanya ke `email`. Framer mengirim nama setiap input sebagai key JSON, dan Ikiform memakai `email` untuk reply-to dan pemeriksaan domain sekali pakai.
2. Pilih formulirnya. Di samping **Send To**, pilih **Add…** › **Webhook** dan tempel endpoint Anda, `https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx`. Signing secret bersifat opsional; Ikiform tidak memeriksa tanda tangan Framer.
3. Di Ikiform, biarkan **Allowed domains** kosong dan spam check mati. Jika salah satunya aktif, setiap kiriman Framer akan ditandai atau ditolak, karena tidak membawa origin dan tidak bisa menjalankan pemeriksaan browser.
4. Atur [rate limit](https://ikiform.com/id/docs/rate-limits.md) ke Relaxed, atau Custom di hari peluncuran. Standard menghitung semua kiriman Framer sebagai satu pengunjung, dan seluruh formulir dibatasi 300 per menit.
5. Atur gaya state sukses formulir di Framer. State ini muncul saat Ikiform menjawab `201`.

> Kiriman yang ditolak Ikiform, seperti email yang tidak ada saat definisi field terpasang, gagal dengan cara yang sama di kelima percobaan ulang Framer. Periksa pengirimannya di Framer jika pendaftaran berhenti masuk.

## Jalur 2: code component

Di panel **Assets**, buka **Code**, buat file kode bernama `WaitlistSignup`, lalu tempel ini. Seret ke halaman dan atur **Form ID** di panel properti.

`WaitlistSignup.tsx`:

```tsx
import { addPropertyControls, ControlType } from "framer";
import { type CSSProperties, type FormEvent, useRef, useState } from "react";

const box: CSSProperties = {
  font: "inherit",
  padding: "12px 14px",
  borderRadius: 10,
  border: "1px solid rgba(0, 0, 0, 0.15)",
};

interface Props {
  formId: string;
  buttonLabel: string;
  joinedText: string;
  source: string;
}

export default function WaitlistSignup({ formId, buttonLabel, joinedText, source }: Props) {
  const [status, setStatus] = useState<"idle" | "sending" | "joined">("idle");
  const [error, setError] = useState("");
  const key = useRef("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const body = new FormData(event.currentTarget);
    const ref = new URLSearchParams(window.location.search).get("ref");
    body.set("ref", (ref || source).slice(0, 40));
    key.current ||= crypto.randomUUID();
    setStatus("sending");
    setError("");
    try {
      const response = await fetch(`https://ikiform.com/f/${formId}`, {
        method: "POST",
        headers: { Accept: "application/json", "Idempotency-Key": key.current },
        body,
      });
      const result = await response.json();
      if (result.ok) return setStatus("joined");
      setError(result.error.fields?.email?.[0] ?? result.error.message);
    } catch {
      setError("Couldn't reach the server. Try again.");
    }
    setStatus("idle");
  }

  if (status === "joined") return <p role="status" style={{ margin: 0 }}>{joinedText}</p>;

  return (
    <form onSubmit={onSubmit} style={{ display: "flex", flexWrap: "wrap", gap: 8, width: "100%" }}>
      <input
        name="email"
        type="email"
        placeholder="you@company.com"
        aria-label="Email"
        autoComplete="email"
        required
        style={{ ...box, flex: "1 1 220px" }}
      />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      <button type="submit" disabled={status === "sending"} style={{ ...box, cursor: "pointer" }}>
        {status === "sending" ? "Joining" : buttonLabel}
      </button>
      {error ? <p role="alert" style={{ flexBasis: "100%", margin: 0 }}>{error}</p> : null}
    </form>
  );
}

addPropertyControls(WaitlistSignup, {
  formId: { type: ControlType.String, title: "Form ID", defaultValue: "frm_xxxxxxxxxxxxxxxx" },
  buttonLabel: { type: ControlType.String, title: "Button", defaultValue: "Join the waitlist" },
  joinedText: { type: ControlType.String, title: "Joined text", defaultValue: "You're on the list." },
  source: { type: ControlType.String, title: "Source", defaultValue: "framer" },
});
```

- Panel properti menampilkan label tombol, teks setelah bergabung, dan **Source** default. Taruh satu instance di hero dengan `hero` dan satu lagi di footer dengan `footer`, dan Anda akan melihat mana yang lebih banyak menghasilkan pendaftaran.
- `?ref=` di URL halaman lebih diutamakan daripada default, jadi link kampanye menandai dirinya sendiri.
- Idempotency key dibuat saat pengiriman pertama dan dipakai ulang pada percobaan ulang dari kunjungan yang sama, jadi ketukan ganda di ponsel hanya menyimpan satu pendaftaran.
- `window` hanya dibaca di dalam submit handler. Framer merender komponen di server lebih dulu, dan menyentuh `window` saat render akan merusaknya.

### Pengaturan Ikiform untuk komponen

Tambahkan ini di tab **Fields** formulir, agar email wajib diisi dan `ref` diizinkan:

`Field`:

```json
{
  "version": 1,
  "title": "Waitlist",
  "fields": [
    { "id": "email", "type": "email", "label": "Email", "required": true },
    { "id": "ref", "type": "text", "label": "Source", "validation": { "max_length": 40 } }
  ]
}
```

- Di **Allowed domains**, tambahkan `your-project.framer.app` dan domain kustom Anda. Jangan pernah menambahkan `framer.app` saja; setiap situs Framer akan cocok.
- Biarkan spam check mati. Komponen mengirim dengan `fetch` dan tidak mengambil token, jadi formulir dengan pemeriksaan aktif akan menjawab `403 challenge_required`.
- Pertahankan rate limit Standard. Setiap pengunjung kini dihitung masing-masing.

## Pastikan berfungsi

1. Publikasikan situsnya. Webhook dan code component hanya mengirim dari situs yang sudah dipublikasikan, bukan dari preview editor.
2. Kirim formulir di domain `framer.app` Anda, lalu lagi di domain kustom Anda.
3. Buka **Submissions** di dashboard. Kiriman baru ada di sana dengan setiap field yang Anda kirim.
4. Untuk komponen, buka halaman dengan `?ref=test` dan pastikan pendaftaran menampilkan `ref` sebagai `test`.
5. Periksa inbox setiap penerima notifikasi. Masing-masing mendapat email berisi field-field tersebut, dan **Reply** ditujukan ke pengirim jika formulir punya field `email`.
6. Tidak ada yang masuk? Ubah filter status di dashboard ke **Spam**. Spam mendapat jawaban sukses yang sama dengan kiriman asli.

## Jebakan

| Masalah | Yang harus dilakukan |
| --- | --- |
| Setiap pendaftaran webhook masuk spam dengan `origin_mismatch` | **Allowed domains** sudah diatur, sedangkan kiriman dari server Framer tidak punya origin. Kosongkan untuk formulir ini, atau beralih ke code component. |
| Framer melaporkan pengiriman webhook yang gagal | Ikiform menolak kirimannya: `403 challenge_required` karena spam check aktif, atau `422` karena nama input bukan `email`. Perbaiki penyebabnya; percobaan ulang akan gagal dengan cara yang sama. |
| `429 rate_limited` | Satu pengunjung mengirim melebihi rate limit formulir. Standard mengizinkan 5 per menit per pengunjung. Tunggu sesuai `Retry-After`, atau pilih Relaxed atau Custom untuk formulir yang mengirim dari satu IP bersama. |
| Komponen menampilkan `Unknown field "ref"` | Definisi field tidak mencantumkan `ref`. Tambahkan seperti pada definisi di atas. |
| `403 origin_not_allowed`, atau setiap kiriman masuk ke spam | Domain halaman tidak ada di **Allowed domains** formulir. Tambahkan, ditambah `localhost` untuk tes lokal. Subdomain dari domain yang tercantum diizinkan. |

Setup Framer lengkap, termasuk versi formulir kontak, ada di [panduan dokumentasi Framer](https://ikiform.com/id/docs/guides/framer.md). Ekspor daftar dan pengiriman email peluncuran ada di [panduan utama](https://ikiform.com/id/guides/waitlist.md#launch-day).

## Pertanyaan

### Bisakah formulir Framer mengirim ke URL eksternal?

Tidak sebagai action formulir. Formulir Framer mengirim ke tujuan milik Framer sendiri, dan salah satunya adalah webhook ke URL https mana pun. Gunakan itu untuk Ikiform, atau render formulir Anda sendiri dengan code component.

### Mengapa pendaftaran daftar tunggu Framer saya ditandai sebagai spam?

Dengan jalur webhook, Framer mengirim dari servernya tanpa header Origin, jadi formulir yang mengatur Allowed domains menandai setiap kiriman sebagai origin_mismatch. Biarkan Allowed domains kosong untuk formulir webhook.

### Apakah pendaftaran lewat webhook Framer dihitung dalam batas formulir Framer?

Framer menyatakan kiriman formulir tidak terbatas, jadi pendaftaran lewat webhook tidak terbentur batas Framer. Jalur code component sama sekali tidak memakai formulir Framer.

### Apakah Ikiform gratis?

Tidak, tidak ada paket gratis. Pengujian gratis: Anda bisa mengirim 50 kiriman uji per bulan dari localhost sebelum membayar. Untuk go live, Pro mulai $6/bln untuk 500 kiriman, atau Anda bisa bayar sekali untuk 10.000 kiriman seharga $49 atau 50.000 kiriman seharga $149, dengan kredit yang tidak pernah kedaluwarsa.

## Panduan lainnya

- [Daftar tunggu untuk SaaS baru](https://ikiform.com/id/guides/waitlist.md): Bangun daftar tunggu pra-peluncuran di landing page Anda sendiri: formulir, perlindungan spam, duplikat, email konfirmasi, dan ekspor rapi di hari peluncuran.
- [Daftar tunggu di Webflow](https://ikiform.com/id/guides/waitlist/webflow.md): Arahkan Form Block Webflow ke Ikiform, tandai pendaftaran menurut sumbernya dengan field tersembunyi, dan pertahankan success state Webflow dengan skrip singkat untuk setiap formulir.
- [Daftar tunggu di Carrd](https://ikiform.com/id/guides/waitlist/carrd.md): Kumpulkan pendaftaran daftar tunggu di halaman Carrd: formulir Send to URL di Pro Plus dengan pelacakan UTM, atau formulir HTML yang di-embed dengan honeypot di Pro Standard.
- [Daftar tunggu dengan HTML biasa](https://ikiform.com/id/guides/waitlist/html.md): Halaman daftar tunggu lengkap tanpa build dengan HTML biasa yang tetap berfungsi saat JavaScript mati, menampilkan ucapan terima kasih inline saat skrip dimuat, dan punya fallback bila perlu.
- [Daftar peluncuran Product Hunt](https://ikiform.com/id/guides/waitlist/product-hunt.md): Kumpulkan daftar peluncuran berminggu-minggu sebelum Product Hunt, tandai setiap pendaftaran per channel, dan kirim email hari-H dalam dua gelombang mengikuti reset waktu Pasifik.
- [Pendaftaran beta dengan penyaringan](https://ikiform.com/id/guides/waitlist/beta-signup.md): Formulir lamaran beta dengan pertanyaan penyaringan yang ditegakkan server, dan skrip yang menilai pelamar serta memilih setiap kohort dari daftar.
