ビルダーのフォームを卒業する2 分で読めます
Framer のフォーム:フォームバックエンドを追加すべきときと、その方法
Framer のフォームはメール、Sheets、Webhook に送れます。保存とフィルタリング、チームへの通知、送信先の追加がほしいなら Ikiform を Webhook にします。
Framer のネイティブなフォームは、各送信をメール、Google スプレッドシート、Webhook に送り、スパム対策も内蔵しているので、受信トレイ 1 つかシート 1 つで足りるなら、ほかには何も要らないかもしれません。すべての送信をスパムの状態付きで保存し、フィールドに照らして検証し、チームにメールで送り、複数の署名付き Webhook に転送したいなら、Ikiform を Webhook の送信先に追加します。Framer は自社のサーバーから送信するので、フォームのオリジンチェックを緩めるか、Framer の署名を検証する小さなリレーを間に置いてください。
Framer ならではの違い
このガイドのビルダーで、抜け出すべき上限がないのは Framer だけです。フォームはメール、Google スプレッドシート、任意の Webhook に送れ、疑わしい送信にフラグを付けたりブロックしたりするスパム対策も内蔵しています。Webhook は署名付きです。各リクエストには、ボディと送信 ID の HMAC-SHA256 である Framer-Signature ヘッダーと、Framer-Webhook-Submission-Id が付きます。Framer はエンドポイントが 2xx を返さないと最大 5 回リトライし、リダイレクトはたどりません。
だからこそ、フォームバックエンドを追加する理由は正直に考えてください。それぞれがカバーする範囲は次のとおりです。
| したいこと | Framer だけ | Ikiform を Webhook にした場合 |
|---|---|---|
| 受信トレイ 1 つかシート 1 つに届く送信データ | はい | はい、ダッシュボードも |
| スパムが区別された検索できる記録と、CSV エクスポート | 受信トレイかシートが記録 | はい |
| 型、長さ、選択肢のサーバー側のチェック | いいえ | はい、フィールド定義があれば |
| 複数人への通知。返信は送信者に届く | メールの送信先 | 最大 10 人の受信者、reply-to 付き |
| 1 つのフォームから複数のツールに送り、それぞれリトライ | 追加した送信先ごとに Webhook 1 つ | 署名付きエンドポイント最大 3 つ、配信ログ付きでリトライ |
| Framer、Webflow、コードのサイトで同じバックエンド | いいえ | はい |
1 行目だけで足りるなら、Framer の送信先のままにしてください。このページの残りは、足りない場合のためのものです。
落とし穴:Framer は自社のサーバーから送信する
Webhook の送信先では、送信するのは訪問者のブラウザーではなく Framer のサーバーです。Ikiform 側では 3 つのことが変わります。
- オリジンがない。 サーバーからの送信には
Originヘッダーがないので、Allowed domains を設定していると、すべての送信が違うサイトから来たものとして扱われます。 - IP アドレスが共有される。 Ikiform はレート制限を IP アドレスごとに数えますが、すべての送信が訪問者のアドレスではなく、少数の Framer のアドレスから来ます。
- ハニーポットがない。 Framer は作った入力欄しか送らず、Framer のフォームでは Ikiform の人間確認を実行できません。
これを受け入れるか、リレーで解決するかです。両方の方法を以下に示します。
- 直接:訪問者が Framer のフォームを送信し、Framer のサーバーが Origin ヘッダーなしの JSON で送り、Ikiform は Allowed domains が空で Relaxed のレート制限なら受け付けます。
- リレー:Framer のサーバーがすべての送信にあなたのシークレットで署名し、あなたがデプロイした Worker に送ります。
- Worker が Framer の署名を計算し直します。有効な署名のない送信には 401 が返り、先には進みません。
- 有効な送信は、シークレットキーと、冪等性キーとしての Framer の送信 ID を付けて Ikiform に転送されるので、Framer がリトライしても 2 つ目のコピーは保存されません。
方法 1:Ikiform を Webhook にする
- Framer で各入力欄に小文字の名前を付けます:
name、work_email、company、team_size。Framer はこの名前を JSON のキーに使います。 - フォームを選び、Send To の横で Add… > Webhook を選びます。エンドポイントを貼り付けます。シークレットは空のままにします。Ikiform は
Framer-Signatureを確認しません。 - Ikiform のフォームの設定で、Allowed domains を空のまま、Spam check をオフのままにし、Rate limit を Relaxed にします。
- 公開します。Framer が Webhook を送るのは公開したサイトからだけです。
https://ikiform.com/f/frm_xxxxxxxxxxxxxxxxIkiform は送信データを保存すると 201 を返し、Framer はそれを配信完了とみなします。422 validation_failed は 5 回のリトライすべてで同じように失敗するので、待たずに入力欄の名前を直してください。スパムスコアはすべての送信で引き続き計算されます。リンク、スパムの言い回し、使い捨てアドレス、繰り返しです。
方法 2:署名付きリレー
Framer の署名を確認し、有効な送信をシークレットキー付きで転送する Cloudflare Worker をデプロイします。そうすれば Ikiform のフォームの Allowed domains に Framer のドメインを入れて Reject にでき、自分のサイト上のブラウザーか Worker 以外からの送信はすべて拒否されます。
interface Env {
FRAMER_SECRET: string;
IKIFORM_SECRET_KEY: string;
}
const ENDPOINT = "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx";
const encoder = new TextEncoder();
async function signatureFor(secret: string, body: ArrayBuffer, submissionId: string) {
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
const id = encoder.encode(submissionId);
const message = new Uint8Array(body.byteLength + id.byteLength);
message.set(new Uint8Array(body), 0);
message.set(id, body.byteLength);
const digest = new Uint8Array(await crypto.subtle.sign("HMAC", key, message));
return `sha256=${[...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
}
function sameText(a: string, b: string) {
const left = encoder.encode(a);
const right = encoder.encode(b);
return left.byteLength === right.byteLength && crypto.subtle.timingSafeEqual(left, right);
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== "POST") return new Response(null, { status: 405 });
const signature = request.headers.get("framer-signature") ?? "";
const submissionId = request.headers.get("framer-webhook-submission-id") ?? "";
const body = await request.arrayBuffer();
if (!submissionId || !sameText(signature, await signatureFor(env.FRAMER_SECRET, body, submissionId))) {
return new Response("Bad signature", { status: 401 });
}
const response = await fetch(ENDPOINT, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${env.IKIFORM_SECRET_KEY}`,
"Idempotency-Key": submissionId,
},
body,
});
if (response.ok) return new Response(null, { status: 204 });
return new Response(await response.text(), { status: response.status });
},
};デプロイする
npx wrangler secret put FRAMER_SECRET
npx wrangler secret put IKIFORM_SECRET_KEY
npx wrangler deployFRAMER_SECRETは、Framer の Webhook の送信先に設定するシークレットです。Framer は 32 文字以上を求め、二度と表示しないので、生成して保管し、両方の場所に貼り付けてください。IKIFORM_SECRET_KEYは、Ikiform のダッシュボードで作るシークレット API キーです。Framer には絶対に入れないでください。- 署名は、
sha256=に、生のボディのあとに送信 ID を続けたものの HMAC を 16 進数にした値を付けたものです。ボディはバイト列として 1 回だけ読み、その同じバイト列を確認と転送の両方に使ってください。 Idempotency-Keyがあると、Framer のリトライには重複を保存する代わりに Ikiform の最初の応答がそのまま返ります。キーは 24 時間保持されます。- Worker は Ikiform のステータスをそのまま返します。Framer は
2xx以外の応答を最大 5 回リトライするので、429や5xxにはもう一度チャンスがありますが、422は入力欄の名前を直すまで毎回同じように失敗します。
そのあと、Framer の Webhook をエンドポイントではなく Worker の URL に向け、シークレットを入力します。
IKIFORM_SECRET_KEY はサーバー専用の環境変数に置いてください。方法 3:Framer のフォームを使わない
コードコンポーネントは本物のフォームを描画して訪問者のブラウザーから送信するので、オリジンチェック、ハニーポット、訪問者ごとのレート制限がほかのサイトと同じように機能します。そのフォームでは Framer のビジュアルなフォーム編集が使えなくなります。コンポーネントは Framer のドキュメントガイドにあります。
Framer で動作を確認する
- Framer のサイトを公開し、エディターのプレビューではなく公開したサイトでフォームを送信します。
- ダッシュボードで Submissions を開きます。送ったすべてのフィールドを含む新しい送信データがあります。
- フィールド名が入力欄の名前と完全に一致し、Framer が追加したキーがないことを確認します。
- リレーの場合:でたらめな
Framer-Signatureを付けて Worker にリクエストを送り、401が返ることを確認します。 - リレーの場合:送信データの詳細で、オリジンが空で、ステータスがスパムではなく受信済みになっていることを確認します。
- 通知の各受信者の受信トレイを確認します。それぞれにフィールド入りのメールが届き、フォームに
emailフィールドがあれば Reply で送信者に返信できます。 - Webhook を追加した場合は、Webhooks でエンドポイントを開きます。配信が
succeededになり、サーバーの応答が表示されます。
ハマりどころ
| 問題 | 対処法 |
|---|---|
すべての Webhook の送信が origin_mismatch でスパムになる | Framer が直接送信しているのに Allowed domains が設定されています。空にするか、リレーを間に置いてください。 |
Worker が本物の送信に 401 を返す | Framer と Worker でシークレットが違うか、何かがボディをテキストとして読んで変更しています。シークレットを貼り直し、生のバイト列でハッシュを計算してください。 |
ローンチ中に 429 rate_limited になる | すべての送信が Framer か Cloudflare のアドレスを共有しています。Relaxed か Custom の制限を選んでください。 |
422 validation_failed | 送信がフォームのフィールド定義に合っていません。error.fields に各フィールドと理由が示されます。入力欄の name をフィールド ID に合わせてください。 |
403 challenge_required | フォームのスパムチェックが Ikiform に設定されていて、fetch の送信にトークンがありませんでした。送信のたびに @ikiform/sdk の challenge(formId) でトークンを取得するか、チェックをオフにしてください。 |
よくある質問
必ずしも必要ではありません。Framer は単体で、送信をメール、Google スプレッドシート、Webhook に送れます。保存されフィルタリングされた記録、サーバー側の検証、reply-to 付きのチームへの通知、1 つのフォームからリトライ付きの複数の Webhook がほしいときに Ikiform を追加してください。
いいえ。Ikiform は送信をオリジンかシークレット API キーで認証し、Framer のヘッダーは使いません。Framer-Signature を確認するには、Framer と Ikiform の間に、署名を検証してシークレットキー付きで転送する小さなリレーを置いてください。
たいていは Allowed domains が設定されているからです。Framer の Webhook の送信は Framer のサーバーから Origin ヘッダーなしで届くので、どのドメインとも一致しません。一覧を空にするか、シークレットキーで送信するリレーを使ってください。
直接送信している場合、Ikiform が保存したあとに配信がリトライされると重複することがあります。リレーは Framer の送信 ID を冪等性キーとして送るので、リトライには最初の応答がそのまま返ります。
まだできません。ファイルを含む送信は 422 files_not_supported で拒否されます。代わりに、共有ドライブの URL などファイルへのリンクを尋ねてください。
手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com