Webhook
署名付きの Webhook ペイロード、HMAC-SHA256 署名の検証、リトライのスケジュール、配信ログ。
Webhook は、受け付けた送信データを届いたそばからあなたのサーバーに送ります。配信は Standard Webhooks 方式で署名され、約 3 日間リトライされ、ダッシュボードに試行ごとに記録されます。すぐに使えるハンドラーは Webhook を受け取るにあります。
エンドポイントを追加する
- ダッシュボードで Webhooks を開き、Add endpoint を選びます。
- HTTPS の URL を入力し、フォームを 1 つ、またはワークスペースのすべてのフォームを選びます。
- 署名シークレットをコピーします。
whsec_で始まり、全体が表示されるのは 1 回だけです。 - テストイベントを送って、ハンドラーを確認します。
エンドポイントの URL
https:のみ。デフォルトのポート 443 で、URL にユーザー名やパスワードを含めず、最大 2,048 文字。- 公開されたホスト名。IP アドレス、
localhost、.local、.internal、.intranet、.lan、.home.arpa、.onionで終わる名前は拒否されます。ikiform.com、workers.dev、pages.dev も同様です。 - ホスト名は公開アドレスだけに解決される必要があります。プライベート、ループバック、リンクローカル、キャリアグレード NAT の範囲は拒否されます。
- URL は保存時と、毎回の配信の前にチェックされます。プライベートアドレスに解決されるようになったエンドポイントは無効化されます。
- 1 つのワークスペースに登録できるエンドポイントは 3 件までです。Enterprise には上限がありません。
イベント
| タイプ | 送信されるタイミング | リトライ |
|---|---|---|
submission.created | 送信データが受け付けられたとき、またはスパムの送信データがスパムではないとマークされたとき | あり |
endpoint.test | エンドポイントのページで Send test を選んだとき | なし(1 回のみ) |
スパムで Webhook が送られることはありません。ワークスペースが月間の上限を超えて保留された送信データでも送られません。
リクエスト
各配信は JSON のボディと 3 つの署名ヘッダーを持つ POST で、Ikiform の配信サービスからユーザーエージェント Ikiform-Webhooks/1.0 で送られます。
POST /webhooks/ikiform HTTP/1.1
content-type: application/json
user-agent: Ikiform-Webhooks/1.0
webhook-id: dlv_01k7v2d4n8r2p6s0w3y5b7c9e1
webhook-timestamp: 1792300000
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=| ヘッダー | 値 |
|---|---|
webhook-id | 配信 ID。同じ配信のリトライでは毎回同じなので、重複の無視に使います |
webhook-timestamp | この試行に署名した時刻の Unix 秒。試行ごとに新しくなります |
webhook-signature | v1, に base64 の HMAC-SHA256 を続けたもの。シークレットのローテーション中は、スペース区切りの複数の値になります |
ペイロード
{
"id": "dlv_01k7v2d4n8r2p6s0w3y5b7c9e1",
"type": "submission.created",
"timestamp": "2026-10-17T12:00:00.000Z",
"api_version": "2026-10-17",
"data": {
"submission_id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3",
"form": { "id": "frm_xxxxxxxxxxxxxxxx", "name": "Contact" },
"fields": { "name": "Ada", "email": "ada@example.com", "message": "Hello" },
"meta": {
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"origin": "https://example.com",
"referrer": "https://example.com/contact",
"country": "GB",
"subject": null,
"reply_to": "ada@example.com"
},
"created_at": "2026-10-17T12:00:00.000Z",
"truncated": false
}
}| フィールド | 説明 |
|---|---|
id | 配信 ID。webhook-id と同じ |
type | submission.created または endpoint.test |
timestamp | 配信が作成された日時(ISO 8601、UTC)。リトライでも同じ |
api_version | エンドポイントの作成時のペイロードのバージョン。現在は 2026-10-17 |
data.submission_id | 送信データの ID。REST API で取得できます |
data.form | フォームの id と name |
data.fields | 保存された値。フィールドが定義されている場合は、バリデーションと型変換を経た値 |
data.meta | REST API の meta と同じキー。すべてのキーが存在し、どの値も null になりえます。訪問者の IP やそのハッシュが送られることはありません |
data.meta.user_agent | ブラウザーのユーザーエージェント。最大 512 文字 |
data.meta.origin, referrer | ブラウザーが送った場合の、送信元 |
data.meta.country | 2 文字の国コード。Cloudflare から取得 |
data.meta.subject, reply_to | _subject と _replyto フィールドから |
data.created_at | 送信データが保存された日時 |
data.truncated | ボディが 256 KB を超える場合は true で、その場合 fields は空になります |
meta の値はどれも null になりえます。フィールドの値は、自分のサーバーへのフォーム送信と同じく、信頼できない入力として扱ってください。
署名を検証する
HMAC-SHA256。鍵は whsec_ 以降を base64 デコードしたシークレット
webhook-signature: v1,base64(signature)
- リクエストの生のボディを、受け取ったとおりに使ってください。JSON を解析して再シリアライズするとバイト列が変わります。
- ヘッダーには、スペース区切りで複数の署名が入ることがあります。いずれかの
v1署名が一致すれば、リクエストを受け入れます。 - 比較は定数時間で行い、現在時刻から 5 分以上ずれたタイムスタンプは拒否してください。
SDK を使う
verifyWebhook は上記のすべてを Web Crypto で行い、解析済みのイベントを返すか、reason 付きの WebhookVerificationError を投げます。Headers オブジェクトも、Node の req.headers のような単純なレコードも受け付けます。
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";
export async function POST(request: Request) {
const payload = await request.text();
try {
const event = await verifyWebhook({
payload,
headers: request.headers,
secret: process.env.IKIFORM_WEBHOOK_SECRET!,
});
if (event.type === "submission.created") {
console.log(event.data.fields);
}
return new Response(null, { status: 204 });
} catch (error) {
if (error instanceof WebhookVerificationError) {
return new Response(error.reason, { status: 400 });
}
throw error;
}
}SDK を使わない
Standard Webhooks のどのライブラリでも動きます。手で書いても数行です:
import { createHmac, timingSafeEqual } from "node:crypto";
const secret = Buffer.from(process.env.IKIFORM_WEBHOOK_SECRET.replace(/^whsec_/, ""), "base64");
export function verify(body, headers) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
const signatures = headers["webhook-signature"];
if (!id || !timestamp || !signatures) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${id}.${timestamp}.${body}`).digest();
return signatures.split(" ").some((entry) => {
const [version, signature] = entry.split(",");
if (version !== "v1" || !signature) return false;
const received = Buffer.from(signature, "base64");
return received.length === expected.length && timingSafeEqual(received, expected);
});
}署名の検証ツール
ブラウザー内の Web Crypto で SDK の verifyWebhook を使います。貼り付けた内容がこのページの外に出ることはありません。
応答する
| あなたの応答 | Ikiform の動作 |
|---|---|
15 秒以内の 2xx | 配信を成功として記録 |
410 Gone | 配信を失敗にし、エンドポイントをすぐに無効化 |
Retry-After 付きの 429 または 503 | その時間が経ってからリトライ。ただしスケジュールの次の段階より遅くはしない |
| リダイレクト | 従わず、失敗として扱う |
| その他のステータス、タイムアウト、ネットワークエラー | 下記のスケジュールでリトライ |
時間のかかる処理は、キューなどを使って応答のあとに行ってください。レスポンスボディの先頭 4 KB が配信ログに保存されます。
リトライ
失敗した試行は、このスケジュールでリトライされます。各待ち時間には前後最大 20% のランダムなジッターが入ります。9 回目の試行(初回から約 3日6時間後)のあと、配信は失敗として記録されます。
- 1すぐ0秒
- 25秒5秒
- 35分5分
- 430分35分
- 52時間2時間35分
- 65時間7時間35分
- 710時間17時間35分
- 81日1日18時間
- 91日12時間3日6時間
濃いバーは最短、薄いバーは最長の待ち時間で、前後 20% のジッターによるものです。9 回目の試行のあと、配信は失敗として記録されます。
無効化されたエンドポイント
エンドポイントへの配信が 5 日間すべて失敗すると、エンドポイントは無効化され、ワークスペースのオーナーにメールが届きます。410 Gone の応答や、送信先がプライベートになった場合はすぐに無効化されます。問題を直したら、エンドポイントのページから再びオンにしてください。
配信ログ
各エンドポイントのページには、配信がステータス付きで一覧表示されます。1 つを展開すると、すべての試行を確認できます。実行日時、自動・手動・テストのどれか、ステータスコード、所要時間、レスポンスの先頭 4 KB またはエラーです。そこからどの配信でもリトライできます。試行は 30 日間、配信は 90 日間保持されます。
シークレットのローテーション
Roll signing secret で新しいシークレットを作成します。その後 24 時間は、すべての配信が古いシークレットと新しいシークレットの両方で署名されるので、webhook-signature には 2 つの値が入り、イベントを取りこぼさずに新しいシークレットをデプロイできます。