ビルダーのフォームを卒業する

ビルダーのフォームを卒業する2 分で読めます

Framer のフォーム:フォームバックエンドを追加すべきときと、その方法

Framer のフォームはメール、Sheets、Webhook に送れます。保存とフィルタリング、チームへの通知、送信先の追加がほしいなら Ikiform を Webhook にします。

Preet SutharIkiform 創業者

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 のフォームが Ikiform に直接送信する場合と、署名付きリレー経由で送信する場合直接 Webhook署名付きリレーFramerのフォームFramerのサーバーJSON、オリジンなしIkiformドメインは空、Relaxedの制限FramerのフォームFramerのサーバーすべての送信に署名あなたのWorkerFramer-Signatureを確認Ikiformシークレットキー、冪等性キー401、破棄送信Webhook送信Webhook転送不正な署名Framer のフォームが Ikiform に直接送信する場合と、署名付きリレー経由で送信する場合直接 Webhook署名付きリレーFramer のフォームFramer のサーバーJSON、オリジンなしIkiformドメインは空、Relaxed の制限Framer のフォームFramer のサーバーすべての送信に署名あなたの WorkerFramer-Signatureを確認Ikiformシークレットキー、冪等性キー401、破棄送信Webhook送信Webhook転送不正な署名
  1. 直接:訪問者が Framer のフォームを送信し、Framer のサーバーが Origin ヘッダーなしの JSON で送り、Ikiform は Allowed domains が空で Relaxed のレート制限なら受け付けます。
  2. リレー:Framer のサーバーがすべての送信にあなたのシークレットで署名し、あなたがデプロイした Worker に送ります。
  3. Worker が Framer の署名を計算し直します。有効な署名のない送信には 401 が返り、先には進みません。
  4. 有効な送信は、シークレットキーと、冪等性キーとしての Framer の送信 ID を付けて Ikiform に転送されるので、Framer がリトライしても 2 つ目のコピーは保存されません。
リレーの実質的なロジックは十数行です。その代わり、シークレットキーを持つのは Worker だけなので、フォームは厳格な許可ドメインを維持できます。

方法 1:Ikiform を Webhook にする

  1. Framer で各入力欄に小文字の名前を付けます:name、work_email、company、team_size。Framer はこの名前を JSON のキーに使います。
  2. フォームを選び、Send To の横で Add… > Webhook を選びます。エンドポイントを貼り付けます。シークレットは空のままにします。Ikiform は Framer-Signature を確認しません。
  3. Ikiform のフォームの設定で、Allowed domains を空のまま、Spam check をオフのままにし、Rate limit を Relaxed にします。
  4. 公開します。Framer が Webhook を送るのは公開したサイトからだけです。
Webhook の URL
https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx

Ikiform は送信データを保存すると 201 を返し、Framer はそれを配信完了とみなします。422 validation_failed は 5 回のリトライすべてで同じように失敗するので、待たずに入力欄の名前を直してください。スパムスコアはすべての送信で引き続き計算されます。リンク、スパムの言い回し、使い捨てアドレス、繰り返しです。

方法 2:署名付きリレー

Framer の署名を確認し、有効な送信をシークレットキー付きで転送する Cloudflare Worker をデプロイします。そうすれば Ikiform のフォームの Allowed domains に Framer のドメインを入れて Reject にでき、自分のサイト上のブラウザーか Worker 以外からの送信はすべて拒否されます。

src/index.ts
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 deploy
  • FRAMER_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 に向け、シークレットを入力します。

これですべての送信が Worker から来ます。サーバー側の版はシークレットキー付きで送信するので、Ikiform は Allowed domains と人間確認をスキップします。その場合はすべての送信がサーバーの IP から来るため、訪問者ごとに 1 分あたり 5 件という Standard の制限が、すべての訪問者をまとめて数えてしまいます。サーバー経由で送信するフォームでは Relaxed か Custom のレート制限を選び、IKIFORM_SECRET_KEY はサーバー専用の環境変数に置いてください。

方法 3:Framer のフォームを使わない

コードコンポーネントは本物のフォームを描画して訪問者のブラウザーから送信するので、オリジンチェック、ハニーポット、訪問者ごとのレート制限がほかのサイトと同じように機能します。そのフォームでは Framer のビジュアルなフォーム編集が使えなくなります。コンポーネントは Framer のドキュメントガイドにあります。

Framer で動作を確認する

  1. Framer のサイトを公開し、エディターのプレビューではなく公開したサイトでフォームを送信します。
  2. ダッシュボードで Submissions を開きます。送ったすべてのフィールドを含む新しい送信データがあります。
  3. フィールド名が入力欄の名前と完全に一致し、Framer が追加したキーがないことを確認します。
  4. リレーの場合:でたらめな Framer-Signature を付けて Worker にリクエストを送り、401 が返ることを確認します。
  5. リレーの場合:送信データの詳細で、オリジンが空で、ステータスがスパムではなく受信済みになっていることを確認します。
  6. 通知の各受信者の受信トレイを確認します。それぞれにフィールド入りのメールが届き、フォームに email フィールドがあれば Reply で送信者に返信できます。
  7. 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) でトークンを取得するか、チェックをオフにしてください。

よくある質問

手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com