# Framer

> Kirim formulir Framer ke Ikiform lewat tujuan webhook tanpa kode, atau render formulir Anda sendiri dengan code component yang mengirim dari browser.

Formulir Framer tidak bisa mengirim ke URL action eksternal, tetapi bisa mengirim setiap kiriman ke webhook. Itu memberi dua jalan: pertahankan formulir Framer dan tambahkan Ikiform sebagai webhook-nya, atau pasang code component kecil yang mengirim dari browser pengunjung.

|   | Formulir Framer dengan webhook | Code component |
| --- | --- | --- |
| Siapa yang mengirim | Server Framer | Browser pengunjung |
| Kode | Tidak ada | Satu file React |
| Domain yang diizinkan | Biarkan kosong: kiriman dari server tidak membawa `Origin` | Dicocokkan dengan domain Framer Anda |
| Honeypot | Tidak tersedia | Termasuk |
| Rate limit dihitung per | Alamat server Framer | Setiap pengunjung |
| Pemeriksaan spam Ikiform | Biarkan mati | Biarkan mati |

## Formulir Framer dengan webhook

### Langkah 1: Beri nama input

Framer mengirim JSON dengan nama setiap input sebagai key, jadi nama yang Anda berikan ke input menjadi nama field di Ikiform. Gunakan ID huruf kecil seperti `email` dan `message`, dan samakan dengan [definisi field](https://ikiform.com/id/docs/fields.md) Anda jika ada.

### Langkah 2: Tambahkan Ikiform sebagai webhook

Pilih formulir di kanvas. Di samping **Send To** di sidebar kanan, pilih **Add…** > **Webhook** dan tempel endpoint Anda. Framer mewajibkan URL `https://`, dan endpoint Anda sudah memakainya.

`Webhook URL`:

```text
https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx
```

Framer bisa menandatangani request dengan secret. Ikiform tidak memeriksa header `Framer-Signature`, jadi secret bersifat opsional di sini.

### Langkah 3: Sesuaikan pengaturan formulir Ikiform

- Biarkan **Allowed domains** kosong. Framer mengirim dari servernya tanpa header `Origin`, jadi jika domain diatur, setiap kiriman ditandai spam dengan `origin_mismatch`, atau ditolak dengan **Reject**.
- Biarkan pemeriksaan spam mati. Kiriman JSON tanpa token mendapat `403 challenge_required`, dan Framer tidak punya cara untuk menjalankan pemeriksaan.
- Pilih [rate limit](https://ikiform.com/id/docs/rate-limits.md) **Relaxed**. Ikiform menghitung kiriman per alamat IP dan formulir, dan setiap kiriman berasal dari server Framer, bukan dari pengunjung Anda.

Framer menganggap respons apa pun selain `2xx` sebagai kegagalan dan mencoba ulang hingga 5 kali. Ikiform menjawab `201` begitu kiriman disimpan. Kiriman yang ditolak Ikiform, seperti `422 validation_failed`, akan gagal dengan cara yang sama di setiap percobaan ulang, jadi perbaiki penyebabnya alih-alih menunggu.

## Code component

Code component merender formulir sungguhan di halaman Anda, jadi browser yang mengirimnya dan Ikiform melihat situs Anda sebagai origin. Di panel **Assets**, buka **Code**, buat file kode, lalu tempel:

`IkiformContact.tsx`:

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

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

export default function IkiformContact({ formId, successText }: { formId: string; successText: string }) {
  const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
  const [message, setMessage] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setStatus("sending");
    try {
      const response = await fetch(`https://ikiform.com/f/${formId}`, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(event.currentTarget),
      });
      const result = await response.json();
      if (result.ok) return setStatus("sent");
      setMessage(result.error.message);
    } catch {
      setMessage("Couldn't reach the server. Try again.");
    }
    setStatus("error");
  }

  if (status === "sent") return <p>{successText}</p>;

  return (
    <form onSubmit={onSubmit} style={{ display: "grid", gap: 12, width: "100%" }}>
      <input name="email" type="email" placeholder="Email" autoComplete="email" required style={field} />
      <textarea name="message" placeholder="Message" rows={4} style={field} />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {status === "error" ? <p role="alert">{message}</p> : null}
      <button type="submit" disabled={status === "sending"} style={field}>
        {status === "sending" ? "Sending" : "Send"}
      </button>
    </form>
  );
}

addPropertyControls(IkiformContact, {
  formId: { type: ControlType.String, title: "Form ID", defaultValue: "frm_xxxxxxxxxxxxxxxx" },
  successText: { type: ControlType.String, title: "Success text", defaultValue: "Thanks, we got it." },
});
```

- Seret komponen ke halaman dan atur **Form ID** di panel properti. Ubah gaya input dengan mengedit `field`, atau ganti elemennya dengan milik Anda sendiri.
- `Accept: application/json` membuat Ikiform menjawab dengan JSON, jadi komponen bisa menampilkan hasilnya di tempat.
- Komponen mengirim dengan `fetch`, jadi biarkan pemeriksaan spam formulir mati. Honeypot dan penilaian spam tetap berjalan.

> Anda juga bisa menempel [embed pembuat situs](https://ikiform.com/id/docs/guides/site-builders.md#embed) ke bagian **HTML** di komponen **Embed**. Jika Framer menaruhnya di frame, halaman terima kasih dan error Ikiform tidak akan dimuat di dalamnya, itulah sebabnya embed memakai `target="_top"`. Code component sama sekali menghindari frame.

## Domain yang diizinkan

Ini hanya berlaku untuk code component dan embed. Biarkan domain yang diizinkan kosong untuk webhook.

| Tempat formulir berjalan | Tambahkan ke Domain yang diizinkan |
| --- | --- |
| Subdomain Framer | `your-project.framer.app`, atau `your-project.framer.website` di proyek lama |
| Domain kustom | `example.com`, yang juga mencakup `www.example.com` |
| Pratinjau editor Framer | Tidak perlu. Uji di situs yang sudah dipublikasikan. |

- Jangan pernah menambahkan `framer.app` atau `framer.website` saja. Setiap situs di host itu akan cocok, termasuk milik orang lain.

## Daftar periksa pengujian

1. Publikasikan situs. Framer hanya mengirim webhook untuk formulir di situs yang sudah dipublikasikan.
2. Kirim formulir dan periksa kirimannya di Ikiform. Dengan webhook, field-nya seharusnya sama persis dengan nama input Anda.
3. Untuk code component, aktifkan domain yang diizinkan, kirim dari domain yang sudah dipublikasikan, dan pastikan tidak ditandai spam.

## Pemecahan masalah

| Yang Anda lihat | Alasan | Solusi |
| --- | --- | --- |
| Setiap kiriman webhook masuk ke spam dengan `origin_mismatch` | **Allowed domains** diatur, dan kiriman dari server Framer tidak punya origin | Kosongkan domain yang diizinkan untuk formulir ini, atau beralih ke code component. |
| Framer melaporkan pengiriman webhook yang gagal | Ikiform menolak kirimannya: `403 challenge_required`, `403 origin_not_allowed`, atau `422 validation_failed` | Matikan pemeriksaan spam, kosongkan domain yang diizinkan, dan samakan nama input dengan field Anda. |
| `429 rate_limited` di formulir yang ramai | Kiriman dari server Framer berbagi sedikit alamat IP | Pilih **Relaxed**, atau batas Custom. |
| Komponen menyatakan tidak bisa terhubung ke server | Ad blocker atau ekstensi privasi memblokir request, atau **Form ID** kosong | Coba jendela privat dan pastikan properti sudah diatur. |
| Kiriman masuk ke filter spam dengan `origin_mismatch` | Halaman yang mengirim tidak ada di domain yang diizinkan formulir | Tambahkan host dari tabel di atas. Jika **Reject** dipilih, kiriman yang sama mendapat `403 origin_not_allowed`. |

## Terkait

- [React](https://ikiform.com/id/docs/guides/react.md): State pending, error, dan sukses.
- [Pembuat situs](https://ikiform.com/id/docs/guides/site-builders.md): Embed yang berfungsi di pembuat situs mana pun.
- [Rate limit](https://ikiform.com/id/docs/rate-limits.md): Preset dan cara pengisiannya kembali.
- [Perlindungan spam](https://ikiform.com/id/docs/spam.md): Honeypot, penilaian, dan pemeriksaan manusia.
