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
- Öffne im Dashboard Webhooks und wähle Add endpoint.
- Gib eine HTTPS-URL ein und wähle ein Formular oder alle Formulare im Workspace.
- Kopiere das Signatur-Secret. Es beginnt mit
whsec_und wird nur einmal vollständig angezeigt. - 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,
localhostund Namen, die auf.local,.internal,.intranet,.lan,.home.arpaoder.onionenden, 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
| Typ | Gesendet, wenn | Erneut versucht |
|---|---|---|
submission.created | Eine Einsendung angenommen wird oder eine Spam-Einsendung als kein Spam markiert wird | Ja |
endpoint.test | Du auf der Endpunkt-Seite Send test wählst | Nein, 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.
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 | Wert |
|---|---|
webhook-id | Die Zustellungs-ID. Bei jedem erneuten Versuch derselben Zustellung gleich, nutze sie also, um Duplikate zu ignorieren |
webhook-timestamp | Unix-Sekunden, zu denen dieser Versuch signiert wurde. Bei jedem Versuch neu |
webhook-signature | v1, 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
}
}| Feld | Beschreibung |
|---|---|
id | Die Zustellungs-ID, gleich wie webhook-id |
type | submission.created oder endpoint.test |
timestamp | Wann die Zustellung erstellt wurde, in ISO 8601 UTC. Bei jedem erneuten Versuch gleich |
api_version | Die Payload-Version, mit der der Endpunkt erstellt wurde, derzeit 2026-10-17 |
data.submission_id | Die ID der Einsendung. Rufe sie mit der REST API ab |
data.form | id und name des Formulars |
data.fields | Die gespeicherten Werte. Wenn Felder definiert sind, sind das die validierten, umgewandelten Werte |
data.meta | Dieselben 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_agent | Der User-Agent des Browsers, bis zu 512 Zeichen |
data.meta.origin, referrer | Woher der Post kam, sofern der Browser sie gesendet hat |
data.meta.country | Zweibuchstabiger Ländercode, von Cloudflare |
data.meta.subject, reply_to | Aus den Feldern _subject und _replyto |
data.created_at | Wann die Einsendung gespeichert wurde |
data.truncated | true, 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
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.
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
Nutzt verifyWebhook aus dem SDK mit Web Crypto in deinem Browser. Nichts, was du einfügst, verlässt diese Seite.
Antworten
| Deine Antwort | Was Ikiform macht |
|---|---|
Ein beliebiger 2xx innerhalb von 15 Sekunden | Markiert die Zustellung als erfolgreich |
410 Gone | Lässt die Zustellung fehlschlagen und deaktiviert den Endpunkt sofort |
429 oder 503 mit Retry-After | Versucht es nach dieser Verzögerung erneut, aber nicht später als beim nächsten Schritt im Zeitplan |
| Eine Weiterleitung | Wird nicht befolgt. Zählt als Fehlschlag |
| Jeder andere Status, ein Timeout oder ein Netzwerkfehler | Erneute 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.
- 1jetzt0 s
- 25 s5 s
- 35 min5 min
- 430 min35 min
- 52 h2 h 35 min
- 65 h7 h 35 min
- 710 h17 h 35 min
- 81 T1 T 18 h
- 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.