Formular-ID

Webhooks

Signierte Webhook-Payloads, Prüfung der HMAC-SHA256-Signatur, der Zeitplan für erneute Versuche und Zustellungsprotokolle.

Webhooks schicken jede angenommene Einsendung an deinen Server, sobald sie eintrifft. Zustellungen werden nach dem Standard Webhooks-Schema signiert, etwa drei Tage lang erneut versucht und im Dashboard Versuch für Versuch protokolliert. Fertige Handler findest du unter Webhooks empfangen.

Füge einen Endpunkt hinzu

  1. Öffne im Dashboard Webhooks und wähle Add endpoint.
  2. Gib eine HTTPS-URL ein und wähle ein Formular oder alle Formulare im Workspace.
  3. Kopiere das Signatur-Secret. Es beginnt mit whsec_ und wird nur einmal vollständig angezeigt.
  4. Sende ein Test-Event, um deinen Handler zu prüfen.

Endpunkt-URLs

  • Nur https:, auf dem Standardport 443, ohne Benutzername oder Passwort in der URL, bis zu 2.048 Zeichen.
  • Ein öffentlicher Hostname. IP-Adressen, localhost und Namen, die auf .local, .internal, .intranet, .lan, .home.arpa oder .onion enden, werden abgelehnt, ebenso ikiform.com, workers.dev und pages.dev.
  • Der Hostname darf nur zu öffentlichen Adressen auflösen. Private, Loopback-, Link-Local- und Carrier-Grade-NAT-Bereiche werden abgelehnt.
  • Die URL wird beim Speichern und erneut vor jeder Zustellung geprüft. Ein Endpunkt, der plötzlich zu einer privaten Adresse auflöst, wird deaktiviert.
  • Ein Workspace kann bis zu 3 Endpunkte haben. Enterprise hat kein Limit.

Events

TypGesendet, wennErneut versucht
submission.createdEine Einsendung angenommen wird oder eine Spam-Einsendung als kein Spam markiert wirdJa
endpoint.testDu auf der Endpunkt-Seite Send test wählstNein, ein Versuch

Spam löst nie einen Webhook aus. Ebenso wenig Einsendungen, die zurückgehalten werden, weil der Workspace über seinem monatlichen Limit liegt.

Der Request

Jede Zustellung ist ein POST mit einem JSON-Body und drei Signatur-Headern, gesendet vom Zustelldienst von Ikiform mit dem 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=
HeaderWert
webhook-idDie Zustellungs-ID. Bei jedem erneuten Versuch derselben Zustellung gleich, nutze sie also, um Duplikate zu ignorieren
webhook-timestampUnix-Sekunden, zu denen dieser Versuch signiert wurde. Bei jedem Versuch neu
webhook-signaturev1, plus ein Base64-HMAC-SHA256. Mehrere durch Leerzeichen getrennte Werte, während ein Secret rotiert wird

Payloads

{
  "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
  }
}
FeldBeschreibung
idDie Zustellungs-ID, gleich wie webhook-id
typesubmission.created oder endpoint.test
timestampWann die Zustellung erstellt wurde, in ISO 8601 UTC. Bei jedem erneuten Versuch gleich
api_versionDie Payload-Version, mit der der Endpunkt erstellt wurde, derzeit 2026-10-17
data.submission_idDie ID der Einsendung. Rufe sie mit der REST API ab
data.formid und name des Formulars
data.fieldsDie gespeicherten Werte. Wenn Felder definiert sind, sind das die validierten, umgewandelten Werte
data.metaDieselben Schlüssel wie meta in der REST API. Jeder Schlüssel ist vorhanden, und jeder Wert kann null sein. Die IP des Besuchers und ihr Hash werden nie gesendet
data.meta.user_agentDer User-Agent des Browsers, bis zu 512 Zeichen
data.meta.origin, referrerWoher der Post kam, sofern der Browser sie gesendet hat
data.meta.countryZweibuchstabiger Ländercode, von Cloudflare
data.meta.subject, reply_toAus den Feldern _subject und _replyto
data.created_atWann die Einsendung gespeichert wurde
data.truncatedtrue, wenn der Body über 256 KB groß wäre; in diesem Fall ist fields leer

Jeder meta-Wert kann null sein. Behandle Feldwerte als nicht vertrauenswürdige Eingaben, genau wie einen Formular-Post an deinen eigenen Server.

Prüfe die Signatur

Wie die Webhook-Signatur berechnet wird
webhook-id.webhook-timestamp.roher Body

HMAC-SHA256, mit dem base64-dekodierten Secret nach whsec_ als Schlüssel

webhook-signature: v1,base64(signature)

  • Verwende den rohen Request-Body genau so, wie er empfangen wurde. Parsen und erneutes Serialisieren von JSON verändert die Bytes.
  • Der Header kann mehrere durch Leerzeichen getrennte Signaturen enthalten. Akzeptiere den Request, wenn irgendeine v1-Signatur passt.
  • Vergleiche in konstanter Zeit und lehne Zeitstempel ab, die mehr als 5 Minuten von jetzt abweichen.

Mit dem SDK

verifyWebhook erledigt all das mit Web Crypto und gibt das geparste Event zurück oder wirft WebhookVerificationError mit einem reason. Es akzeptiert ein Headers-Objekt oder ein einfaches Record wie req.headers in 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;
  }
}

Ohne das SDK

Jede Standard-Webhooks-Bibliothek funktioniert ebenfalls. Von Hand sind es nur ein paar Zeilen:

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);
  });
}

Signaturprüfer

5-Minuten-Fenster ignorieren

Nutzt verifyWebhook aus dem SDK mit Web Crypto in deinem Browser. Nichts, was du einfügst, verlässt diese Seite.

Antworten

Deine AntwortWas Ikiform macht
Ein beliebiger 2xx innerhalb von 15 SekundenMarkiert die Zustellung als erfolgreich
410 GoneLässt die Zustellung fehlschlagen und deaktiviert den Endpunkt sofort
429 oder 503 mit Retry-AfterVersucht es nach dieser Verzögerung erneut, aber nicht später als beim nächsten Schritt im Zeitplan
Eine WeiterleitungWird nicht befolgt. Zählt als Fehlschlag
Jeder andere Status, ein Timeout oder ein NetzwerkfehlerErneute Versuche nach dem Zeitplan unten

Erledige langsame Arbeit nach dem Antworten, zum Beispiel in einer Queue. Die ersten 4 KB deines Response-Bodys werden im Zustellungsprotokoll gespeichert.

Erneute Versuche

Ein fehlgeschlagener Versuch wird nach diesem Zeitplan erneut versucht, jede Verzögerung mit bis zu 20 % zufälligem Jitter in beide Richtungen. Nach dem 9. Versuch, etwa 3 Tage und 6 Stunden nach dem ersten, wird die Zustellung als fehlgeschlagen markiert.

Zeitplan für Webhook-Wiederholungen, mit der Wartezeit vor jedem Versuch auf einer logarithmischen Skala
VersuchWartezeit davorSeit dem ersten
  1. 1jetzt0 s
  2. 25 s5 s
  3. 35 min5 min
  4. 430 min35 min
  5. 52 h2 h 35 min
  6. 65 h7 h 35 min
  7. 710 h17 h 35 min
  8. 81 T1 T 18 h
  9. 91 T 12 h3 T 6 h

Dunkle Balken zeigen die kürzeste Wartezeit und helle die längste, bei 20 % Jitter in beide Richtungen. Nach Versuch 9 wird die Zustellung als fehlgeschlagen markiert.

Deaktivierte Endpunkte

Wenn 5 Tage lang jede Zustellung an einen Endpunkt fehlschlägt, wird der Endpunkt deaktiviert und die Workspace-Inhaber bekommen eine E-Mail. Eine 410 Gone-Antwort oder ein Ziel, das privat wird, deaktiviert ihn sofort. Schalte ihn auf der Endpunkt-Seite wieder ein, sobald er repariert ist.

Zustellungsprotokolle

Jede Endpunkt-Seite listet ihre Zustellungen mit ihrem Status auf. Klapp eine auf, um jeden Versuch zu sehen: wann er lief, ob er automatisch, manuell oder ein Test war, den Statuscode, die Dauer und die ersten 4 KB deiner Antwort oder den Fehler. Von dort kannst du jede Zustellung erneut senden. Versuche werden 30 Tage und Zustellungen 90 Tage lang aufbewahrt.

Secrets rotieren

Roll signing secret erstellt ein neues Secret. In den nächsten 24 Stunden wird jede Zustellung sowohl mit dem alten als auch mit dem neuen Secret signiert, sodass webhook-signature zwei Werte enthält und du das neue Secret deployen kannst, ohne Events zu verlieren.