Webhook
Payload webhook bertanda tangan, verifikasi tanda tangan HMAC-SHA256, jadwal percobaan ulang, dan log pengiriman.
Webhook mendorong setiap kiriman yang diterima ke server Anda saat itu juga. Pengiriman ditandatangani dengan skema Standard Webhooks, dicoba ulang selama sekitar tiga hari, dan dicatat per percobaan di dashboard. Handler siap pakai ada di Menerima webhook.
Tambahkan endpoint
- Di dashboard, buka Webhooks dan pilih Add endpoint.
- Masukkan URL HTTPS dan pilih satu formulir, atau semua formulir di workspace.
- Salin signing secret. Secret diawali
whsec_dan hanya ditampilkan utuh sekali. - Kirim event tes untuk memeriksa handler Anda.
URL endpoint
- Hanya
https:, di port default 443, tanpa nama pengguna atau kata sandi di URL, hingga 2.048 karakter. - Hostname publik. Alamat IP,
localhost, dan nama yang berakhiran.local,.internal,.intranet,.lan,.home.arpa, atau.onionditolak, begitu pula ikiform.com, workers.dev, dan pages.dev. - Hostname hanya boleh resolve ke alamat publik. Rentang private, loopback, link-local, dan carrier-grade NAT ditolak.
- URL diperiksa saat Anda menyimpannya dan lagi sebelum setiap pengiriman. Endpoint yang mulai resolve ke alamat private akan dinonaktifkan.
- Satu workspace bisa punya hingga 3 endpoint. Enterprise tanpa batas.
Event
| Tipe | Dikirim saat | Dicoba ulang |
|---|---|---|
submission.created | Kiriman diterima, atau kiriman spam ditandai sebagai bukan spam | Ya |
endpoint.test | Anda memilih Send test di halaman endpoint | Tidak, satu percobaan |
Spam tidak pernah memicu webhook. Begitu pula kiriman yang ditahan karena workspace melewati batas bulanannya.
Request-nya
Setiap pengiriman berupa POST dengan body JSON dan tiga header tanda tangan, dikirim dari layanan pengiriman Ikiform dengan user agent 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=| Header | Nilai |
|---|---|
webhook-id | ID pengiriman. Sama di setiap percobaan ulang untuk pengiriman yang sama, jadi gunakan untuk mengabaikan duplikat |
webhook-timestamp | Detik Unix saat percobaan ini ditandatangani. Baru di setiap percobaan |
webhook-signature | v1, ditambah HMAC-SHA256 dalam base64. Beberapa nilai dipisah spasi selama secret dirotasi |
Payload
{
"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
}
}| Field | Deskripsi |
|---|---|
id | ID pengiriman, sama dengan webhook-id |
type | submission.created atau endpoint.test |
timestamp | Waktu pengiriman dibuat, dalam ISO 8601 UTC. Sama di setiap percobaan ulang |
api_version | Versi payload saat endpoint dibuat, saat ini 2026-10-17 |
data.submission_id | ID kiriman. Ambil dengan REST API |
data.form | id dan name formulir |
data.fields | Nilai yang disimpan. Jika field didefinisikan, ini adalah nilai yang sudah divalidasi dan dikonversi |
data.meta | Key yang sama dengan meta di REST API. Setiap key selalu ada dan nilai apa pun bisa null. IP pengunjung dan hash-nya tidak pernah dikirim |
data.meta.user_agent | User agent browser, hingga 512 karakter |
data.meta.origin, referrer | Asal kiriman, jika browser mengirimkannya |
data.meta.country | Kode negara dua huruf, dari Cloudflare |
data.meta.subject, reply_to | Dari field _subject dan _replyto |
data.created_at | Waktu kiriman disimpan |
data.truncated | true jika body melebihi 256 KB, dan dalam hal itu fields kosong |
Nilai meta apa pun bisa null. Perlakukan nilai field sebagai input yang tidak tepercaya, sama seperti kiriman formulir ke server Anda sendiri.
Verifikasi tanda tangan
HMAC-SHA256, dengan kunci berupa secret setelah whsec_ yang sudah di-decode dari base64
webhook-signature: v1,base64(signature)
- Gunakan body request mentah persis seperti yang diterima. Mengurai lalu menyerialisasi ulang JSON akan mengubah byte-nya.
- Header bisa berisi beberapa tanda tangan yang dipisah spasi. Terima request jika ada satu tanda tangan
v1yang cocok. - Bandingkan dalam waktu konstan, dan tolak timestamp yang berselisih lebih dari 5 menit dari sekarang.
Dengan SDK
verifyWebhook melakukan semua hal di atas dengan Web Crypto dan mengembalikan event yang sudah diurai, atau melempar WebhookVerificationError dengan reason. Fungsi ini menerima objek Headers atau record biasa seperti req.headers di Node.
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;
}
}Tanpa SDK
Library Standard Webhooks apa pun juga bisa. Secara manual, hanya butuh beberapa baris:
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);
});
}Verifikator tanda tangan
Memakai verifyWebhook dari SDK dengan Web Crypto di browser Anda. Tidak ada yang Anda tempel yang keluar dari halaman ini.
Merespons
| Respons Anda | Yang dilakukan Ikiform |
|---|---|
2xx apa pun dalam 15 detik | Menandai pengiriman berhasil |
410 Gone | Menggagalkan pengiriman dan langsung menonaktifkan endpoint |
429 atau 503 dengan Retry-After | Mencoba ulang setelah jeda itu, tetapi tidak lebih lambat dari langkah berikutnya di jadwal |
| Redirect | Tidak diikuti. Dihitung sebagai kegagalan |
| Status lain, timeout, atau error jaringan | Mencoba ulang sesuai jadwal di bawah |
Kerjakan pekerjaan yang lambat setelah merespons, misalnya di antrean. 4 KB pertama body respons Anda disimpan di log pengiriman.
Percobaan ulang
Percobaan yang gagal dicoba ulang dengan jadwal ini, masing-masing jeda dengan jitter acak hingga 20% ke dua arah. Setelah percobaan ke-9, sekitar 3 hari 6 jam setelah yang pertama, pengiriman ditandai gagal.
- 1sekarang0 dtk
- 25 dtk5 dtk
- 35 mnt5 mnt
- 430 mnt35 mnt
- 52 jam2 jam 35 mnt
- 65 jam7 jam 35 mnt
- 710 jam17 jam 35 mnt
- 81 hari1 hari 18 jam
- 91 hari 12 jam3 hari 6 jam
Batang gelap menunjukkan jeda terpendek dan batang terang jeda terpanjang, dari jitter 20% ke dua arah. Setelah percobaan ke-9, pengiriman ditandai gagal.
Endpoint yang dinonaktifkan
Jika setiap pengiriman ke sebuah endpoint gagal selama 5 hari, endpoint dinonaktifkan dan pemilik workspace mendapat email. Respons 410 Gone atau tujuan yang menjadi private langsung menonaktifkannya. Aktifkan kembali dari halaman endpoint setelah diperbaiki.
Log pengiriman
Setiap halaman endpoint mencantumkan pengirimannya beserta statusnya. Buka salah satunya untuk melihat setiap percobaan: kapan dijalankan, apakah otomatis, manual, atau tes, kode status, durasi, dan 4 KB pertama respons Anda atau error-nya. Coba ulang pengiriman mana pun dari sana. Percobaan disimpan selama 30 hari dan pengiriman selama 90 hari.
Merotasi secret
Roll signing secret membuat secret baru. Selama 24 jam berikutnya, setiap pengiriman ditandatangani dengan secret lama dan baru sekaligus, jadi webhook-signature berisi dua nilai dan Anda bisa men-deploy secret baru tanpa kehilangan event.