ガイド

ガイド4 分で読めます

HTML フォームの送信の仕組み

フォームが送信されたときにブラウザーが行うこと。どのフィールドを送り、どうエンコードし、サーバーは何を返すべきか、そして Ikiform がどう応答するかを解説します。

Preet SutharIkiform 創業者

送信ボタンが押されると、ブラウザーはフォームの制約をチェックし、name があり無効になっていないコントロールをすべて集め、フォームの enctype でエンコードして、フォームの method で action の URL に送ります。サーバーは通常のページ読み込みと同じように応答します。行儀のよいサーバーは、成功した POST に 303 リダイレクトで応答するので、再読み込みしてもフォームが二重に送信されません。action を Ikiform などのフォームバックエンドに向ければ、自前のサーバーなしで、そのリダイレクト、フィールドの検証、スパムフィルタリングが手に入ります。

HTML フォームは JavaScript より古く、今でもウェブページからデータを送るいちばん確実な方法です。スクリプトがオフでも、遅いスマートフォンでも、マークアップを貼り付けられるどのビルダーでも動きます。フォームのバグの多くは、ブラウザーが自分で何をしているかを知らないことから生まれます。このガイドでは送信を 1 ステップずつたどり、そのあとでフォームバックエンドがどう組み込まれるかを示します。

送信ボタンを押すと何が起きるか

送信は、ボディが付いた通常のナビゲーションです。ブラウザーは、ページから何かが出ていく前に 4 つのことを行い、そのあとリクエストをネットワークに渡して、返ってきたものを読み込みます。

送信ボタンのクリックから次のページまでにブラウザーが行うことブラウザー内ネットワーク経由送信ボタン、Enterキー、またはrequestSubmit()制約チェックrequired、type、pattern、min、maxフォームデータセットnameがあり有効なコントロールのみエンコードurlencoded かmultipartフィールドにメッセージ何も送信されないaction へPOSTSec-Fetch-Mode:navigateサーバーが応答303 SeeOtherLocation をGETサンクスページ有効無効たどる送信ボタンのクリックから次のページまでにブラウザーが行うことブラウザー内ネットワーク経由送信ボタン、Enter キー、またはrequestSubmit()制約チェックrequired、type、pattern、min、maxフォームデータセットnameがあり有効なコントロールのみエンコードurlencoded かmultipartフィールドにメッセージ何も送信されないaction へ POSTSec-Fetch-Mode:navigateサーバーが応答303 See OtherLocation を GETサンクスページ有効たどる無効
  1. 送信ボタン、テキストフィールドでの Enter キー、または form.requestSubmit() で送信が始まり、submit イベントが発生します。
  2. フォームに novalidate がない限り、ブラウザーはすべてのコントロールの制約をチェックします。最初の無効なコントロールにメッセージが表示され、何も送信されません。
  3. ブラウザーは、name があり無効になっていないすべてのコントロールからフォームデータセットを作ります。
  4. そのデータセットをフォームの enctype でエンコードします。指定しなければ urlencoded です。
  5. フォームの method で action の URL にナビゲートし、ボディを送ります。
  6. サーバーが応答します。303 はブラウザーに別の URL を GET で読み込ませ、それが訪問者が最終的に見るページになります。
線より上のことは、ネットワークをまったく使わずに起きます。submit イベントのリスナーなら、最初のステップで流れを止め、fetch で処理を引き継げます。

リクエストを決める属性

<form> の 5 つの属性が、データをどこへどう送るかを決めます。どれも、対応する form* 属性で 1 つのボタンだけ上書きできます。こうして、1 つのフォームに動作の違う Send と Save draft のボタンを置けます。

属性デフォルト役割ボタンでの上書き
actionページ自身の URLデータを受け取る URL。相対値はページを基準に解決されます。action 属性を参照してくださいformaction
methodgetget はフィールドを URL のクエリ文字列に入れます。post はボディとして送ります。dialog は何も送らずに <dialog> を閉じますformmethod
enctypeapplication/x-www-form-urlencodedpost のボディのエンコード方法。ファイルには multipart/form-data が必要です。text/plain はデバッグ用ですformenctype
novalidateオフ送信時にブラウザーの制約チェックを飛ばしますformnovalidate
target_selfレスポンスを読み込む場所。_blank は新しいタブを開き、名前付きの iframe ならページはそのままですformtarget

2 つのボタンで 2 つの動作。2 つ目は検証を飛ばします。クリックされたボタン自身の name と value がデータに加わるので、サーバーはどちらが押されたかがわかります。

2 つの送信ボタン
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <input name="email" type="email" required />
  <textarea name="details" required></textarea>
  <button name="intent" value="report">Send report</button>
  <button name="intent" value="draft" formnovalidate>Save as draft</button>
</form>

どのコントロールが送られるか

ブラウザーが送るのは、HTML 仕様でフォームデータセットと呼ばれるものだけです。送信ログで驚くことの多くは、次のルールから来ています。

  • コントロールには name が必要です。id だけでは何も送られません。
  • disabled のコントロールは飛ばされます。値を表示してロックしたまま送りたいなら readonly を使ってください。
  • チェックされていないチェックボックスやラジオボタンは、false ではなく何も送りません。value のないチェックボックスは、チェックされると on を送ります。
  • 含まれるのは、フォームを送信したボタンだけで、その name と value が入ります。
  • <select multiple> や、name を共有するチェックボックスのグループは、選択された値ごとに 1 組を送ります。
  • <form> 要素の外にある入力欄も、フォームの id を指定した form 属性でフォームに加えられます。

ボディのエンコード方法

デフォルトの enctype では、ボディは & でつないだ name=value の組の 1 行で、スペースは + に、安全な文字以外はすべてパーセントエンコードされます。これは、下にある報告フォームをブラウザーが送ったときの実際のリクエストです。

application/x-www-form-urlencoded
POST /f/frm_xxxxxxxxxxxxxxxx HTTP/1.1
Host: ikiform.com
Content-Type: application/x-www-form-urlencoded
Origin: https://example.com
Sec-Fetch-Mode: navigate

email=ada%40example.com&page=https%3A%2F%2Fexample.com%2Fpricing&severity=broken&details=The+annual+toggle+does+nothing&_redirect=https%3A%2F%2Fexample.com%2Freport-sent&_gotcha=

multipart/form-data は、ブラウザーが選んだ境界文字列で各フィールドを別々のパートに包みます。サイズは大きくなりますが、ファイルを運べます。Ikiform はどちらも読めます。text/plain には値の終わりを確実に判別する方法がないので、Ikiform は 415 unsupported_media_type で拒否します。

multipart/form-data
POST /f/frm_xxxxxxxxxxxxxxxx HTTP/1.1
Host: ikiform.com
Content-Type: multipart/form-data; boundary=----formdata7f3a

------formdata7f3a
Content-Disposition: form-data; name="email"

ada@example.com
------formdata7f3a
Content-Disposition: form-data; name="severity"

broken
------formdata7f3a--
エンコードが何であれ、ページは UTF-8 にしてください。Ikiform はすべてのボディを UTF-8 としてデコードし、ほかの文字コードは 415 unsupported_charset で拒否します。ファイルアップロードにはまだ対応していません。ファイル付きの送信には 422 files_not_supported が返ります。

フォームを送信する 4 つの方法

通常の送信は選択肢の 1 つにすぎません。以下のどの方法も、場面によっては妥当な選択です。違うのは、誰が応答を処理するかと、スクリプトがオフのときに何が起きるかです。

方法得意なことトレードオフ
リダイレクト付きの素の method="post"JavaScript なしで、どのビルダーでも動きます。読み込み中の状態、戻る、再読み込みはブラウザーが処理しますページ全体の読み込み。フィールドエラーには、サーバーで描画したページかバックエンドのエラーページが必要です
FormData を使った fetch再読み込みなし。成功時や各フィールドエラーの見せ方を自分で決められます読み込み中の状態、ネットワークエラー、フォーカスを自分で扱います。気をつけないと、スクリプトがオフのときに壊れます。ページを再読み込みせずに送信するを参照してください
Next.js のサーバーアクションや SvelteKit のフォームアクションなど、フレームワークのフォームアクション送信を最初に受け取るのは自分のサーバーで、同じフォームがハイドレーションの前後どちらでも動きますサーバーのランタイムが必要で、バックエンドへのリクエストは自分のサーバーから出ていくので、レート制限やオリジンのチェックの扱いが変わります
method="get"検索やフィルターのフォーム。結果に共有できる URL が付きますフィールドが URL、履歴、サーバーログに残ります。個人データや状態を変更するものには絶対に使わないでください

まず素の送信から始め、その上に fetch を足してください。そうすれば、スクリプトの読み込みに失敗してもフォームは動きます。これは多くのアナリティクスが示すよりも頻繁に起きます。

サーバーは何を返すべきか

ブラウザーは、POST への応答を新しいページとして扱います。そのページが応答そのもの(ステータス 200)だと、ブラウザーはそれが POST から来たことを覚えています。再読み込みするとフォームを再送信するか尋ねられ、戻ると進むでも同じことをするブラウザーがあります。こうして二重に支払いが起き、サポートに同じメッセージが 3 回届きます。

解決策は Post/Redirect/Get パターンです。サーバーは POST を処理し、Location ヘッダー付きの 303 See Other で応答します。ブラウザーはその URL を GET で読み込むので、訪問者が見るページは GET から来たものになり、再読み込みしても害はありません。

POST にページで応答する場合と 303 リダイレクトで応答する場合の比較ページで応答(200)リダイレクトで応答(303)POST/report200 でサンクスHTMLページは POSTから来たもの再読み込み「フォームの再送信の確認」もう一度 POST重複POST/report303 SeeOtherLocation:/report-sentGET/report-sent再読み込み再読み込みは GET だけPOST にページで応答する場合と 303 リダイレクトで応答する場合の比較ページで応答(200)リダイレクトで応答(303)POST /report200 でサンクス HTMLページは POST から来たもの再読み込み「フォームの再送信の確認」もう一度 POST重複POST /report303 See OtherLocation: /report-sentGET /report-sent再読み込み再読み込みは GET だけ
  1. 200 で応答すると、ブラウザーはフォームを送信し、レスポンスのボディをページとして表示します。
  2. そのページは POST に結びついているので、再読み込みすると再送信を求められ、確認するとデータがもう一度送られます。
  3. 303 で応答すると、ブラウザーはフォームを送信し、別の URL を読み込むよう指示されます。
  4. ブラウザーはその URL を GET で読み込みます。再読み込みで繰り返されるのは GET だけなので、何も二重に送られません。
Ikiform はブラウザーからの送信に常に 303 で応答するので、フォームは何もしなくてもこの動作になります。
ステータスブラウザーが次にすること使う場面
303 See OtherLocation を GET し、ボディは破棄フォームの送信が成功したあとは常に
302 Found実際にはブラウザーは GET に切り替えますが、仕様ではメソッドを維持することも許されています古いサーバー。303 を推奨
307 と 308同じ POST を同じボディで新しい URL に繰り返すエンドポイントの移転。サンクスページには使わない
HTML 付きの 200ページを表示。再読み込みすると再送信を求められるエラー付きでフォームを再表示するとき
HTML 付きの 4xxエラーページを表示訪問者が戻って直せるエラー
Post/Redirect/Get
HTTP/1.1 303 See Other
Location: https://example.com/report-sent
X-Request-Id: req_01k7v2c9t3e8m5q4x6z0a1b2c3

GET /report-sent HTTP/1.1
Host: example.com

Ikiform の応答のしかた

1 つのエンドポイント https://ikiform.com/f/<formId> が、ブラウザーにもコードにも応答します。ページで応答するか JSON で応答するかは、ブラウザーが自動で付けるヘッダーからリクエストごとに判断します。

リクエスト応答
Accept に application/json が含まれるJSON
ボディが JSONJSON
Sec-Fetch-Mode: navigate(素のフォーム送信はすべてこれ)リダイレクトまたはホストされたページ
それ以外の Sec-Fetch-Mode(fetch の cors など)JSON
Sec-Fetch-Mode がなく、Accept に text/html が含まれるリダイレクトまたはホストされたページ
それ以外(curl など)JSON

Sec-Fetch-Mode はブラウザーが付けるヘッダーで、ページのスクリプトからは変更できません。タブを遷移させるフォーム送信は navigate を送り、fetch の呼び出しはクロスサイトのリクエストなら cors を送ります。そのため同じフォームでも、素の方法で送信すればリダイレクトが、スクリプトで送信すれば JSON が返り、こちらで何かを切り替える必要はありません。表の全体は JSON かリダイレクトかにあります。

同じ Ikiform のエンドポイントが、素のフォーム送信と fetch の呼び出しに応答する様子通常のフォーム送信fetch、または Accept: application/jsonブラウザーが遷移Sec-Fetch-Mode:navigateIkiformページモード自分のページへ 303またはホストされたサンクスページホストされたエラーページフィールドのラベルごとの問題、実際のステータススクリプトが送信Sec-Fetch-Mode: corsIkiformJSON モード201 と ID{ ok: true, id }JSON のエラーフィールド ID ごとのerror.fields拒否拒否同じ Ikiform のエンドポイントが、素のフォーム送信と fetch の呼び出しに応答する様子通常のフォーム送信fetch、または Accept: application/jsonブラウザーが遷移Sec-Fetch-Mode:navigateIkiformページモード自分のページへ 303またはホストされたサンクスページホストされたエラーページフィールドのラベルごとの問題、実際のステータススクリプトが送信Sec-Fetch-Mode:corsIkiformJSON モード201 と ID{ ok: true, id }JSONのエラーフィールド IDごとのerror.fields拒否拒否
  1. 素のフォーム送信はタブを遷移させるので、ブラウザーは Sec-Fetch-Mode: navigate を送ります。
  2. Ikiform はページモードで応答します。成功なら 303 リダイレクト、失敗ならエラーの実際のステータスでホストされたエラーページを返します。
  3. fetch の呼び出しは Sec-Fetch-Mode: cors を送るか、コードが Accept ヘッダーで JSON を要求します。
  4. Ikiform は JSON モードで応答します。201 と送信データの ID、またはフィールド ID をキーにしたフィールドエラー付きのエラーエンベロープを返します。
モードを設定する必要はありません。リクエストの送り方で決まります。

送信が成功したあとの行き先

ブラウザーからの送信が成功すると 303 が返ります。Ikiform は次の順番で行き先を選びます。

  1. ダッシュボードのフォームの Redirect after submit 設定。設定できるのは自分のワークスペースだけなので、http: でも https: でも、どの URL でも使えます。
  2. 送信に含まれる _redirect フィールド(_redirect がなければ _next)。ユーザー名、パスワード、独自のポートを含まない https: の URL で、フォームの Allowed domains(許可ドメイン)のどれか、またはそのサブドメインにある場合に限ります。
  3. それ以外の場合は、/f/<formId>/thanks にあるホストされたサンクスページです。送信が許可ドメインの https: のページから来た場合は、そこへ戻るリンクが付きます。

ルールに反する _redirect はエラーにはならず、単に無視されます。許可ドメインのないフォームは _redirect を完全に無視します。これで、誰かがあなたのフォームを使って訪問者を好きなサイトへ飛ばすことを防げます。サンクスページについては送信後のリダイレクトで詳しく扱い、ルールの全体は HTML フォームにあります。

送信が拒否されたとき

ページモードでは、拒否された送信に対して、エラーの実際の HTTP ステータスでホストされたエラーページが表示されます。検証エラーなら、各問題をフィールドのラベルの下に並べ、送信元のページへのリンクを付けます。ブラウザーの Back(戻る)ボタンなら、たいてい訪問者が入力した内容が残ったままフォームに戻れます。リンクはページを新しく読み込みます。JSON モードでは、同じエラーが 1 つのエンベロープで返ります。すべてのコードはエラーにあります。

JSON モードでの 422
{
  "ok": false,
  "error": {
    "code": "validation_failed",
    "message": "Some fields are invalid.",
    "fields": {
      "page": ["Enter a valid URL"],
      "details": ["Must be at least 20 characters"]
    }
  },
  "request_id": "req_01k7v2c9t3e8m5q4x6z0a1b2c3"
}

スパムだけは、決してエラーのようには見えません。スパムと判定された送信にも本物と同じ 303 か 201 が返るので、ボットは何も学べず、メールも Webhook も送られません。

あなたのスタックでのフォーム

壊れたページを報告する実際のフォームを、すべてのスタックで示します。自分のスタックを選んでください。選択はこのガイドのすべてのコード例に引き継がれます。下のフィールド定義は Ikiform がサーバーで強制するものと同じなので、試してみてください。詳細を短くしたり、アドレスに example と入力したりすると、フォームが受け取るのと同じ 422 が返ります。

プレビュー

Ikiform のバリデーターをブラウザーで実行します。何も送信されません。

この定義をダッシュボードのフォームの Fields タブに貼り付けると、Ikiform がすべての送信をこれに照らして検証します。フィールドと検証を参照してください。

スクリプトはまったく使いません。ブラウザーが required、type、minlength をチェックし、フィールドを urlencoded のテキストとして送信し、Ikiform の 303 に従って _redirect のページに移動し、サーバーが送信を拒否したら Ikiform のエラーページを表示します。

report.html
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <label for="email">Your email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />

  <label for="page">Page address</label>
  <input id="page" name="page" type="url" required placeholder="https://example.com/pricing" />

  <label for="severity">How bad is it</label>
  <select id="severity" name="severity">
    <option value="cosmetic">Looks wrong</option>
    <option value="broken">Something doesn't work</option>
    <option value="blocking">I can't continue</option>
  </select>

  <label for="details">What happened</label>
  <textarea id="details" name="details" rows="5" minlength="20" maxlength="4000" required></textarea>

  <input type="hidden" name="_redirect" value="https://example.com/report-sent" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Send report</button>
</form>
サーバー側の版はシークレットキー付きで送信するので、Ikiform は Allowed domains と人間確認をスキップします。その場合はすべての送信がサーバーの IP から来るため、訪問者ごとに 1 分あたり 5 件という Standard の制限が、全訪問者をまとめて数えることになります。サーバー経由で送信するフォームには Relaxed か Custom のレート制限を選び、IKIFORM_SECRET_KEY はサーバー専用の環境変数に置いてください。

各サーバー版は、ブラウザーの Origin ヘッダーがあればそれを転送します。サーバー側の fetch は Origin を送らず、Ikiform はこれを使って、プランのないワークスペースでの無料の localhost テストを見分けるからです。

  • どの版にも本物の method があり、Ikiform に直接送信する版には本物の action もあります。スクリプトがオフでもフォームは送信され、303 を受け取ります。
  • 各入力欄の name が、定義のフィールド id になります。page ではなく url のように一致しないと、Unknown field "url" 付きの 422 になります。
  • 非表示の _gotcha 入力欄がハニーポットです。これを埋めたボットは、何も知らせずにスパムとして保存されます。
  • JavaScript 版は、各フィールドの最初のメッセージを使って error.fields を各入力欄の下に表示します。プレーンな HTML 版はそれを Ikiform のエラーページに任せます。

フレームワークごとの詳細はドキュメントにあります。Next.js、Astro、SvelteKit、Nuxt、React、Vue、サーバーからを参照してください。

2 層のバリデーション

ブラウザーのバリデーションとサーバーのバリデーションは役割が違います。ブラウザーの制約チェック(required、type="email"、minlength、pattern)はすぐにフィードバックを返しますが、誰でも迂回できます。ボットは直接送信しますし、スクリプトはチェックを一切実行しない form.submit() を呼べます。ルールが本当に守られるのはサーバーだけです。

ルールブラウザー側Ikiform のフィールド定義
必須required"required": true
メールアドレスtype="email""type": "email"、任意で allowed_domains
URLtype="url""type": "url"、任意で https_only
長さminlength, maxlengthvalidation.min_length, max_length
選択肢のみ<select>(ボットは無視する)options。それ以外は拒否
余分なフィールドなし不可能定義にないフィールドはすべて拒否

定義がなければ、Ikiform はどんなフィールドでも受け付け、届いたまま保存します。定義を公開すると検証が行われ、数値やチェックボックスは本物の数値と真偽値として保存され、何かが保存される前に 422 validation_failed で送信が拒否されます。両方の層で同じことを伝える方法はバリデーションで説明しています。

JavaScript で送信する

訪問者をページにとどめるには、submit を監視して preventDefault() を呼び、new FormData(form) を fetch で送ります。最初からうまく動くかどうかは、いくつかの細かい点で決まります。

  • ボディが FormData のときは、Content-Type を自分で設定しないでください。ブラウザーが境界(boundary)付きで multipart/form-data を設定します。手書きのヘッダーには境界がないので、ボディを解析できなくなります。
  • Accept: application/json を送ります。Ikiform はいずれにせよ Sec-Fetch-Mode から JSON を選びますが、ヘッダーで意図が明確になりますし、これはブラウザーがプリフライトなしでクロスサイト送信できるリクエストヘッダーの 1 つです。
  • FormData や URLSearchParams のボディは単純なクロスサイトリクエストなので、プリフライトはありません。Content-Type: application/json 付きの JSON ボディだと先にプリフライトが発生します。Ikiform はそれに応答しますが、往復が 1 回増えます。
  • form.submit() はバリデーションと submit イベントを飛ばします。form.requestSubmit() は、クリックと同じように両方を実行します。
  • クリックされたボタンの name と value が重要なら、new FormData(form, event.submitter) のように 2 番目の引数として渡します。

読み込み状態、フォーカス、ネットワークエラーまで含めた完全なパターンはページを再読み込みせずに送信にあります。エンドポイントが受け付けるすべてのヘッダーとボディ形式は、ドキュメントの JavaScript と fetch に載っています。

状況に合わせて選ぶ

以下の各ページは、送信の一部分を取り上げて上の概要より深く掘り下げ、そのケースのコードを示します。

動作を確認する

フォームごとに 1 回、プライベートウィンドウでこれを一通り確認してください。Ikiform に無料プランはありません。テストは無料です。プランのないワークスペースでも、localhost からのテスト送信を月 50 件まで受け付けます。本番運用するには、500 件で月 $6 からの Pro を選ぶか、買い切りにしてください。10,000 件で $49、または 50,000 件で $149 で、クレジットに有効期限はありません。料金を参照してください。

  1. フォームの Allowed domains に自分のサイトのドメインを追加し、開発中は localhost も追加します。空のままにすると、どこからの送信も受け付けます。
  2. ブラウザーの開発者ツールで JavaScript をオフにしてフォームを送信します。それでも 303 でサンクスページに着くはずです。
  3. JavaScript をオンにして Network パネルを開き、もう一度送信します。/f/<formId> へのリクエストは JSON 付きの 201 を返し、ページは再読み込みされないはずです。
  4. 短すぎるメッセージで送信します。プレーンな送信ではフィールドのラベル付きで Ikiform のエラーページが表示され、スクリプト版ではフィールドの下にメッセージが表示されます。
  5. サンクスページで再読み込みを押します。何も再送信されず、2 件目の送信データも現れないはずです。
  6. ダッシュボードで Submissions を開きます。送ったすべてのフィールドを含む新しい送信データがあります。
  7. ハニーポットを埋めて _gotcha=bot で送信します。成功の応答は返りますが、送信データはスパムとして保存され、メールも Webhook も送られません。

トラブルシューティング

問題対処法
送信後に 405 Method Not Allowedフォームが自分の静的ホストに送信しており、そこは POST を受け付けられません。action を確認してください。相対パスや空ではなく、完全な https://ikiform.com/f/<formId> URL でなければなりません。
エンドポイントをタブで開くと 404 not_found が表示される想定どおりです。タブは GET を送り、エンドポイントは POST しか受け付けません。フォームの method が post か確認してください。method のないフォームは、フィールドを URL に入れて GET で送ります。
415 unsupported_media_typeフォームに enctype="text/plain" があるか、スクリプトが Ikiform の読めない形式で送っています。enctype を削除するか、multipart/form-data を使ってください。
422 validation_failed送信がフォームのフィールド定義に合っていません。error.fields に各フィールドと理由が示されます。入力欄の name をフィールド ID に合わせてください。
ブラウザーが自分のページではなく Ikiform のサンクスページに移動する_redirect は、ドメインが Allowed domains にあり、https: の URL でないと無視されます。ドメインを追加するか、代わりにフォーム設定で Redirect after submit を設定してください。
送信データにフィールドが欠けているname がない、disabled になっている、またはチェックされていないチェックボックスです。name を付け、disabled の代わりに readonly を使うか、checkbox フィールドを定義して未チェックのボックスを false として保存させます。
再読み込みでフォームの再送信を求められるページが POST に 200 で応答しています。Ikiform に直接送信すれば必ず 303 が返ります。間に自分のサーバーがあるなら、送信を処理した後にリダイレクトしてください。
403 origin_not_allowed が返る、またはすべての送信がスパムになるページのドメインがフォームの Allowed domains にありません。ドメインと、ローカルテスト用の localhost を追加してください。登録したドメインのサブドメインは許可されます。

よくある質問

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