ID formulir

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

  1. Di dashboard, buka Webhooks dan pilih Add endpoint.
  2. Masukkan URL HTTPS dan pilih satu formulir, atau semua formulir di workspace.
  3. Salin signing secret. Secret diawali whsec_ dan hanya ditampilkan utuh sekali.
  4. 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 .onion ditolak, 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

TipeDikirim saatDicoba ulang
submission.createdKiriman diterima, atau kiriman spam ditandai sebagai bukan spamYa
endpoint.testAnda memilih Send test di halaman endpointTidak, 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.

Header
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=
HeaderNilai
webhook-idID pengiriman. Sama di setiap percobaan ulang untuk pengiriman yang sama, jadi gunakan untuk mengabaikan duplikat
webhook-timestampDetik Unix saat percobaan ini ditandatangani. Baru di setiap percobaan
webhook-signaturev1, 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
  }
}
FieldDeskripsi
idID pengiriman, sama dengan webhook-id
typesubmission.created atau endpoint.test
timestampWaktu pengiriman dibuat, dalam ISO 8601 UTC. Sama di setiap percobaan ulang
api_versionVersi payload saat endpoint dibuat, saat ini 2026-10-17
data.submission_idID kiriman. Ambil dengan REST API
data.formid dan name formulir
data.fieldsNilai yang disimpan. Jika field didefinisikan, ini adalah nilai yang sudah divalidasi dan dikonversi
data.metaKey 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_agentUser agent browser, hingga 512 karakter
data.meta.origin, referrerAsal kiriman, jika browser mengirimkannya
data.meta.countryKode negara dua huruf, dari Cloudflare
data.meta.subject, reply_toDari field _subject dan _replyto
data.created_atWaktu kiriman disimpan
data.truncatedtrue 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

Cara tanda tangan webhook dihitung
webhook-id.webhook-timestamp.body mentah

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 v1 yang 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.

app/api/webhooks/ikiform/route.ts
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

Abaikan jendela 5 menit

Memakai verifyWebhook dari SDK dengan Web Crypto di browser Anda. Tidak ada yang Anda tempel yang keluar dari halaman ini.

Merespons

Respons AndaYang dilakukan Ikiform
2xx apa pun dalam 15 detikMenandai pengiriman berhasil
410 GoneMenggagalkan pengiriman dan langsung menonaktifkan endpoint
429 atau 503 dengan Retry-AfterMencoba ulang setelah jeda itu, tetapi tidak lebih lambat dari langkah berikutnya di jadwal
RedirectTidak diikuti. Dihitung sebagai kegagalan
Status lain, timeout, atau error jaringanMencoba 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.

Jadwal percobaan ulang webhook, dengan jeda sebelum setiap percobaan dalam skala log
PercobaanJeda sebelumnyaSejak pertama
  1. 1sekarang0 dtk
  2. 25 dtk5 dtk
  3. 35 mnt5 mnt
  4. 430 mnt35 mnt
  5. 52 jam2 jam 35 mnt
  6. 65 jam7 jam 35 mnt
  7. 710 jam17 jam 35 mnt
  8. 81 hari1 hari 18 jam
  9. 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.