スパム対策
ハニーポット、署名付きタイムスタンプ、スパムスコア、人間確認、許可ドメイン、レート制限。
どのフォームにも、設定なしで多層の保護が付いています。ハニーポット、署名付きの描画タイムスタンプ、スパムスコア、レート制限です。さらに人間確認を追加したり、フォームを自分のドメインに限定したりできます。
スパムの扱い
スパムと判定された送信はステータス spam で保存され、通常の成功の応答を返すので、ボットは何も学べません。スパムは Webhook もメールも送らず、月間の使用量にもカウントされず、30 日後に削除されます。ダッシュボードか REST API で確認でき、スパムではないとマークすると、その時点で Webhook が送られます。
ハニーポット
人が入力することのない非表示の入力欄を追加します。デフォルトの名前は _gotcha で、フォームの設定の Honeypot field(ハニーポットフィールド)でフォームごとに変更できます。隠し方は HTML フォームを参照してください。
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />スパムスコア
送信ごとに、次のシグナルからスコアを計算します。5 以上でスパムとして保存されます。
| シグナル | ダッシュボードに表示される理由 | スコア |
|---|---|---|
| ハニーポットに入力がある | honeypot | +5 |
| フォームの描画から 2 秒未満で送信された | timestamp_fast | +3 |
| 署名付きの描画タイムスタンプが偽造されている、または 24 時間より古い | timestamp_invalid | +3 |
| 名前や件名のフィールドにリンクがある | url_in_name | +3 |
| 同じフィールドが直近 24 時間以内にこのフォームに送られている | duplicate | +3 |
| リンクが合計 3 つ以上ある | many_urls | +2 |
| 既知のスパムの定型文(22 種類を内蔵) | keyword | +2 |
| 使い捨てメールのドメインのメールアドレス | disposable_email | +2 |
- 描画タイムスタンプが存在するのは、署名付きの
_ikiform_tsを送る SDK か shadcn/ui コンポーネントでフォームを描画した場合だけです。これを含まないプレーンな HTML の送信は減点されません。 - 名前のフィールドとは、名前に
name、firstname、lastname、fullname、surname、subjectを含むフィールドのことで、company、website、domainのような語は含みません。 - スコアにかかわらず必ずスパムになる理由が 2 つあります。許可ドメインによる
origin_mismatchと、Turnstile に接続できない場合のcaptcha_unavailableです。
人間確認
各フォームには、3 つの選択肢を持つ Spam check(スパムチェック)の設定があります。デフォルトはオフなので、フォームを作成しても、既存の fetch や curl の連携はそのまま動きます。
| 選択肢 | 内容 |
|---|---|
| オフ | チャレンジなし。ハニーポット、タイムスタンプ、スコアは引き続き動きます。 |
| Ikiform(推奨) | Ikiform が ikiform.com で動かす Cloudflare Turnstile。Cloudflare のアカウントやキーは不要です。 |
| 独自の Turnstile キー | 自分の Turnstile ウィジェットとシークレットキー。トークンのホスト名は許可ドメインと照合されます。 |
Ikiform の確認の流れ
HTML フォーム
- ブラウザーがフォームを送信
- トークンがまだないので、ikiform.com が短い確認ページを表示
- Turnstile が動き、ページがトークン付きで同じフィールドを再送信
- リダイレクト先またはサンクスページへ 303
JavaScript
- challenge(formId) が非表示の ikiform.com フレームをマウント
- Turnstile が動き、クリックが必要な場合のみ表示
- submit(formId, data, { turnstileToken })
- 送信 ID 付きで 201
シークレットキーを使うサーバー
- Authorization: Bearer ik_sk_... を付けて POST
- Ikiform の確認はスキップ
- 送信 ID 付きで 201
import { challenge, submit } from "@ikiform/sdk";
const form = document.querySelector("form");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const turnstileToken = await challenge("frm_xxxxxxxxxxxxxxxx");
const result = await submit("frm_xxxxxxxxxxxxxxxx", new FormData(form), { turnstileToken });
if (result.ok) form.replaceWith("Thanks, we got it.");
});トークンは 1 回限りで有効期間は 300 秒なので、送信ごとに 1 つ取得してください。トークンのない JSON の送信には challenge_url 付きの 403 challenge_required が、検証に失敗したトークンには 403 captcha_failed が返ります。
{
"ok": false,
"error": {
"code": "challenge_required",
"message": "Complete the human check, then send the form again.",
"challenge_url": "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx/challenge"
},
"request_id": "req_01k7v2c9t3e8m5q4x6z0a1b2c3"
}captcha_unavailable でスパムとして保存されるので、失うことなく元に戻せます。独自の Turnstile キー
サイトキーとシークレットキーをフォームの設定に貼り付けます。シークレットは保存時に暗号化されます。フォームにウィジェットを描画して送信に cf-turnstile-response を追加させるか、トークンを turnstileToken として submit に渡します。有効なトークンのない送信には、シークレット API キーを使った送信も含め、403 captcha_failed が返ります。
許可ドメイン
フォームを置くドメインを Allowed domains(許可ドメイン)に登録します。すると送信は、Origin ヘッダー(オリジンがない場合は Referer)で判定して、それらのドメインかそのサブドメインから送られたものでなければならなくなります。どのサイトからの送信も受け付けるには、一覧を空にしておきます。
| 許可ドメイン | 送信元 | 一致するか |
|---|---|---|
example.com | https://example.com | はい |
example.com | https://www.example.com | はい(サブドメイン) |
example.com | http://localhost:3000 | いいえ。開発中は localhost を追加 |
example.com | https://example.co | いいえ |
app.example.com | https://example.com | いいえ。親ドメインは一致しない |
When a post comes from another site(他のサイトから送信された場合)の設定で、一致しないときの動作を決めます。Mark as spam(デフォルト)か、403 origin_not_allowed で Reject するかです。シークレット API キーを使った送信はこのチェックをスキップします。許可ドメインは、どの _redirect 先に従うか、Ikiform の確認フレームをどこに埋め込めるかも決めます。
レート制限
各フォームには Rate limit のプリセットがあり、訪問者の IP とフォームごとに正確にカウントされます。標準は 1 分に 5 件、緩めは 1 分に 20 件、厳しめは 10 分ごとに 2 件です。詳しいルールはレート制限にあります。