# Framer

> Framer のフォームを、コードなしで Webhook の送信先経由で Ikiform に送るか、ブラウザーから送信するコードコンポーネントで独自のフォームを描画します。

Framer のフォームは外部の action URL には送信できませんが、送信ごとに Webhook へ送ることはできます。そのため方法は 2 つあります。Framer のフォームを使って Ikiform をその Webhook にするか、訪問者のブラウザーから送信する小さなコードコンポーネントを置くかです。

|   | Webhook を使う Framer のフォーム | コードコンポーネント |
| --- | --- | --- |
| 送信するのは | Framer のサーバー | 訪問者のブラウザー |
| コード | なし | React のファイル 1 つ |
| 許可ドメイン | 空にする: サーバーからの送信には `Origin` がない | Framer のドメインと照合 |
| ハニーポット | 使えない | あり |
| レート制限のカウント単位 | Framer のサーバーのアドレス | 訪問者ごと |
| Ikiform のスパムチェック | オフにする | オフにする |

## Webhook を使う Framer のフォーム

### ステップ 1: 入力欄に名前を付ける

Framer は各入力欄の名前をキーにした JSON を送るので、入力欄に付けた名前が Ikiform でのフィールド名になります。`email` や `message` のような小文字の ID を使い、[フィールド定義](https://ikiform.com/ja/docs/fields.md)がある場合はそれに合わせてください。

### ステップ 2: Ikiform を Webhook として追加する

キャンバスでフォームを選択します。右サイドバーの **Send To** の横で **Add…** > **Webhook** を選び、エンドポイントを貼り付けます。Framer は `https://` の URL を求めますが、エンドポイントはもともとそうなっています。

`Webhook の URL`:

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

Framer はシークレットでリクエストに署名できます。Ikiform は `Framer-Signature` ヘッダーを確認しないので、ここではシークレットは任意です。

### ステップ 3: Ikiform のフォーム設定を調整する

- **Allowed domains**（許可ドメイン）は空のままにします。Framer はサーバーから `Origin` ヘッダーなしで送信するため、ドメインを設定すると、すべての送信が `origin_mismatch` でスパムになるか、**Reject** の場合は拒否されます。
- スパムチェックはオフのままにします。トークンのない JSON の送信には `403 challenge_required` が返り、Framer にはチェックを実行する手段がありません。
- **Relaxed**（緩め）の[レート制限](https://ikiform.com/ja/docs/rate-limits.md)を選びます。Ikiform は IP アドレスとフォームごとに送信をカウントしますが、すべての送信は訪問者ではなく Framer のサーバーから届きます。

Framer は `2xx` 以外のレスポンスを失敗として扱い、最大 5 回リトライします。Ikiform は送信データを保存すると `201` で応答します。`422 validation_failed` など、Ikiform が拒否した送信はリトライのたびに同じように失敗するので、待たずに原因を直してください。

## コードコンポーネント

コードコンポーネントはページに本物のフォームを描画するので、ブラウザーがそれを送信し、Ikiform はあなたのサイトをオリジンとして認識します。**Assets** パネルで **Code** を開き、コードファイルを作成して次を貼り付けます:

`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." },
});
```

- コンポーネントをページにドラッグし、プロパティパネルで **Form ID** を設定します。入力欄のスタイルは `field` を編集して変えるか、要素を自分のものに置き換えてください。
- `Accept: application/json` を付けると Ikiform が JSON で応答するので、コンポーネントはその場で結果を表示できます。
- コンポーネントは `fetch` で送信するので、フォームのスパムチェックはオフのままにしてください。ハニーポットとスパムスコアは引き続き動きます。

> [サイトビルダー用の埋め込み](https://ikiform.com/ja/docs/guides/site-builders.md#embed)を **Embed** コンポーネントの **HTML** セクションに貼り付けることもできます。Framer がフレーム内に配置した場合、Ikiform のサンクスページやエラーページはその中では読み込まれません。埋め込みで `target="_top"` を使っているのはそのためです。コードコンポーネントならフレームを完全に避けられます。

## 許可ドメイン

これはコードコンポーネントと埋め込みにだけ当てはまります。Webhook の場合、許可ドメインは空のままにしてください。

| フォームが動く場所 | 許可ドメインに追加するもの |
| --- | --- |
| Framer のサブドメイン | `your-project.framer.app`（古いプロジェクトでは `your-project.framer.website`） |
| 独自ドメイン | `example.com`（`www.example.com` も含まれます） |
| Framer のエディターのプレビュー | 不要です。代わりに公開されたサイトでテストしてください。 |

- `framer.app` や `framer.website` だけを追加しないでください。他の人のサイトも含め、そのホスト上のすべてのサイトが一致してしまいます。

## テストのチェックリスト

1. サイトを公開します。Framer が Webhook を送るのは、公開されたサイトのフォームだけです。
2. フォームを送信し、Ikiform で送信データを確認します。Webhook の場合、フィールドは入力欄の名前と完全に一致しているはずです。
3. コードコンポーネントの場合は、許可ドメインをオンにして公開ドメインから送信し、スパムになっていないことを確認します。

## トラブルシューティング

| 症状 | 原因 | 対処 |
| --- | --- | --- |
| Webhook の送信データがすべて `origin_mismatch` でスパムになる | **Allowed domains**（許可ドメイン）が設定されていて、Framer のサーバーからの送信にはオリジンがない | このフォームの許可ドメインを空にするか、コードコンポーネントに切り替えます。 |
| Framer が Webhook の配信の失敗を報告する | Ikiform が送信を拒否した: `403 challenge_required`、`403 origin_not_allowed`、`422 validation_failed` | スパムチェックをオフにし、許可ドメインを空にし、入力欄の名前をフィールドに合わせます。 |
| 送信の多いフォームで `429 rate_limited` が出る | Framer のサーバーからの送信は、少数の IP アドレスを共有している | **Relaxed**（緩め）かカスタムの制限を選びます。 |
| コンポーネントがサーバーに接続できなかったと表示する | 広告ブロッカーやプライバシー保護の拡張機能がリクエストをブロックした、または **Form ID** が空 | プライベートウィンドウで試し、プロパティが設定されているか確認します。 |
| 送信データが `origin_mismatch` でスパムフィルターの下に届く | 送信したページが、フォームの許可ドメインに含まれていない | 上の表のホストを追加します。**Reject** を選んでいる場合、同じ送信には代わりに `403 origin_not_allowed` が返ります。 |

## 関連ページ

- [React](https://ikiform.com/ja/docs/guides/react.md): 送信中、エラー、成功の状態。
- [サイトビルダー](https://ikiform.com/ja/docs/guides/site-builders.md): どのビルダーでも使える埋め込み。
- [レート制限](https://ikiform.com/ja/docs/rate-limits.md): プリセットと回復の仕組み。
- [スパム対策](https://ikiform.com/ja/docs/spam.md): ハニーポット、スコアリング、人間確認。
