Webhooks
Bestätigte Anfragen nach Make, Zapier, n8n — oder an deinen eigenen Endpunkt — signiert und versioniert.
Wenn jemand auf deiner Markenseite eine Anfrage bestätigt, kann Ikrames sie per POST an eine Adresse schicken, die du wählst. Das ist die ganze Funktion: geordnete Übergabe, kein CRM. Was du auf der anderen Seite baust, gehört dir.
Einrichten
- Geh auf Anfragen ▸ Übergabe und trag die Adresse deines Endpunkts ein. Sie muss
httpssein. - Kopier das Signatur-Geheimnis. Es wird genau einmal gezeigt. Verlierst du es, kannst du es wechseln — aber ein Wechsel entwertet jede Automatisierung, die noch das alte benutzt.
- Drück Test senden. Du bekommst eine Beispiel-Nutzlast mit
.invalid-Daten, damit deine Automatisierung die Form lernen kann, ohne eine echte Anfrage.
Die Ereignisse
Es gibt vier. Jeder aktive Endpunkt einer Marke bekommt alle.
ereignis | Wann es ausgelöst wird |
|---|---|
lead.confirmed | Die Person hat den Bestätigungslink in ihrer E-Mail geklickt — nicht, als das Formular abgeschickt wurde. Eine unbestätigte Anfrage ist noch keine Anfrage. |
lead.file_uploaded | Eine Datei wurde an eine bestätigte Anfrage gehängt. Immer nach lead.confirmed für diese Anfrage — eine Datei kann nicht vor der Bestätigung ankommen. |
lead.deleted | Die Marke hat die Anfrage aus ihrem Posteingang entfernt. Was du mit deiner Kopie machst, ist deine Entscheidung. |
lead.erasure_requested | Die Person hat Löschung verlangt. Das ist keine Benachrichtigung. Siehe unten. |
lead.erasure_requested verpflichtet dich
Wenn du lead.erasure_requested bekommst, lösch diesen Datensatz — und alles, was du
daraus abgeleitet hast. Die Nutzlast trägt "pflicht": "loeschen", damit du auf ein
Feld verzweigen kannst statt auf einen Namen. Unsere Kopie haben wir bereits gelöscht;
bleibt deine liegen, ist das Löschverlangen der Person nur halb erfüllt, und die
verbliebene Hälfte liegt in deinem System, nicht in unserem.
lead.deleted ist der andere Fall und trägt mit Absicht einen anderen Namen: dass eine
Marke ihren Posteingang aufräumt, sagt nichts über die Wünsche dieser Person.
Die zwei Löschereignisse tragen keine personenbezogenen Daten
Keine Adresse, keine Antworten, keine Dateinamen — nur anfrage.id, dieselbe Kennung,
die du schon mit lead.confirmed bekommen hast. Eine Adresse zu schicken, um ihre
Löschung anzukündigen, wäre genau die Verarbeitung, die enden soll. Gleich über die ID
ab.
Die Nutzlast
{
"nutzlast_fassung": "2026-09-23",
"ereignis": "lead.confirmed",
"event_id": "8f14e45f-…",
"erzeugt_at": "2026-09-23T10:00:00.000Z",
"marke": { "name": "Studio Nord" },
"formular": {
"titel": "Illustrations-Anfrage",
"slug": "illustration",
"fassung": 2,
"bedingungen": "Zwei Revisionsrunden sind enthalten.",
"fassung_hash": "sha256:1f3a…"
},
"gruppe": null,
"anfrage": {
"id": "…",
"email": "[email protected]",
"eingegangen_at": "2026-09-23T09:58:00.000Z",
"bestaetigt_at": "2026-09-23T10:00:00.000Z",
"einwilligung_fassung": "2026-09-23",
"antworten": [
{ "schluessel": "projektziel", "label": "Worum geht es?", "wert": "Ein Buchcover, Aquarell." }
],
"anhaenge": 2,
"posteingang_url": "https://ikrames.com/anfragen"
}
}Zwei Dinge, die man dazu wissen sollte:
- Antworten tragen Beschriftungen, nicht nur Schlüssel.
labelist die Frage, wie die Person sie wirklich gelesen hat — aus einer Kopie, die bei der Anfrage liegt, und deshalb auch dann richtig, wenn du das Formular später änderst. - Es gibt keine Datei-Adressen. Anhänge gehören der Person, die sie hochgeladen
hat;
anhaengesagt dir, wie viele es sind, undposteingang_urlführt dich hinter deinem Login zu ihnen. Ein Link, der in einer Notion-Zeile überlebt, wäre kein Link, den wir zurücknehmen könnten.
Die anderen drei Nutzlasten
Sie teilen denselben Umschlag — nutzlast_fassung, ereignis, event_id,
erzeugt_at, marke, formular — und unterscheiden sich nur im Rumpf.
lead.file_uploaded:
{
"ereignis": "lead.file_uploaded",
"anfrage": { "id": "…", "anhaenge": 2, "posteingang_url": "https://ikrames.com/anfragen" },
"datei": { "typ": "application/pdf", "bytes": 248512 }
}anhaenge ist die Anzahl nach diesem Upload; nimm also das Feld, statt Ereignisse
zu zählen — ein erneuter Versuch liesse dich doppelt zählen. Einen Dateinamen gibt es
nicht: der Name einer fremden Datei ist oft selbst schon eine Preisgabe.
lead.deleted und lead.erasure_requested:
{
"ereignis": "lead.erasure_requested",
"anfrage": { "id": "…" },
"pflicht": "loeschen"
}pflicht steht nur bei lead.erasure_requested.
Testen
Der Knopf Test senden in Übergabe schickt jede der vier Formen an deinen Endpunkt.
Ein Test trägt immer "ereignis": "test" und benennt die Form in test_fuer, damit du
Testläufe an einem einzigen stabilen Feld abweisen kannst — sonst wäre eine Probe von
lead.deleted nicht vom Ernstfall zu unterscheiden.
Fassungen
nutzlast_fassung versioniert dieses Schema. formular.fassung_hash versioniert
das Formular, das die Person beantwortet hat. Sie ändern sich aus verschiedenen
Gründen — häng deine Automatisierung an das erste und benutz das zweite, um zu merken,
wann ein Formular geändert wurde.
Idempotenz
event_id ist je Zustellung eindeutig. Wir versuchen es bei Fehlschlag erneut, dieselbe
event_id kann also mehr als einmal ankommen — speicher sie und überspring Doppelte.
Die Signatur prüfen
Jede Anfrage trägt:
Ikrames-Signature: t=1758600000,v1=<64 Hex-Zeichen>
Ikrames-Event-Id: <uuid>Signiert wird `${t}.${rawBody}`, und der Schlüssel wird aus deinem Geheimnis
abgeleitet:
key = HMAC_SHA256(key = <dein Geheimnis>, msg = "ikrames:uebergabe-webhook:v1")
v1 = HMAC_SHA256(key = key, msg = `${t}.${rawBody}`) // hexBeispiel in Node:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header.trim());
if (!m) return false;
const t = Number(m[1]);
if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSeconds) return false;
const key = createHmac("sha256", secret).update("ikrames:uebergabe-webhook:v1").digest();
const expected = createHmac("sha256", key).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(m[2], "utf8");
return a.length === b.length && timingSafeEqual(a, b);
}Ein durchgerechnetes Beispiel zum Einfügen in einen Test — Geheimnis whsec_beispiel,
t = 1758600000, Rumpf {"ereignis":"test"}:
v1 = da447fda5709e61fd58df14be025f88cad14a66c4d1ef6b3a03eb131f6c8aa85Drei Einzelheiten, die man leicht übersieht:
- Signier den rohen Rumpf, nicht ein neu serialisiertes Objekt.
JSON.parsegefolgt vonJSON.stringifyändert Schlüsselreihenfolge und Leerzeichen, und die Signatur passt nicht mehr. - Prüf den Zeitstempel. Ohne ihn kann jeder, der einmal eine gültige Anfrage mitgeschnitten hat, sie für immer wiederholen. Fünf Minuten sind ein vernünftiges Fenster.
- Vergleich in gleichbleibender Zeit.
===auf Hex-Zeichenketten verrät, wie viel von der Signatur richtig war.
Erneute Versuche und Pausieren
Eine fehlgeschlagene Zustellung wird mit wachsendem Abstand wiederholt. Scheitert ein Ziel zehnmal hintereinander, pausieren wir es und sagen das auf der Seite Übergabe, mit dem Grund. Alle Wiederholungen zu einer Anfrage zählen als ein Fehlschlag — ein einzelner Ausfall bei dir pausiert also nichts.
Ein pausiertes Ziel schickt nichts, bis du Wieder aktivieren drückst.
Was wir aufheben
Das Zustellprotokoll hält Zustand, Anzahl der Versuche und Antwortcode fest. Die Nutzlast selbst wird nach sieben Tagen gelöscht — sie ist eine zweite Kopie der Anfrage, und die Anfrage liegt schon in deinem Posteingang.