Entwickler

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

  1. Geh auf Anfragen ▸ Übergabe und trag die Adresse deines Endpunkts ein. Sie muss https sein.
  2. 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.
  3. 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.

ereignisWann es ausgelöst wird
lead.confirmedDie 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_uploadedEine 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.deletedDie Marke hat die Anfrage aus ihrem Posteingang entfernt. Was du mit deiner Kopie machst, ist deine Entscheidung.
lead.erasure_requestedDie 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. label ist 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; anhaenge sagt dir, wie viele es sind, und posteingang_url fü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}`)   // hex

Beispiel 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 = da447fda5709e61fd58df14be025f88cad14a66c4d1ef6b3a03eb131f6c8aa85

Drei Einzelheiten, die man leicht übersieht:

  • Signier den rohen Rumpf, nicht ein neu serialisiertes Objekt. JSON.parse gefolgt von JSON.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.

War das hilfreich?