Grenzen von Baukasten-Formularen

Grenzen von Baukasten-Formularen7 Min. Lesezeit

Framer-Formulare: wann ein Formular-Backend sinnvoll ist und wie

Framer-Formulare senden bereits an E-Mail, Sheets oder einen Webhook; ergänze Ikiform als Webhook für gespeicherte, gefilterte Einsendungen, Team-Benachrichtigungen und weitere Ziele.

Preet SutharGründer, Ikiform

Die nativen Formulare von Framer senden jede Einsendung an E-Mail, Google Sheets oder einen Webhook, mit eingebautem Spamschutz. Für einen Posteingang oder eine Tabelle brauchst du also vielleicht nichts weiter. Ergänze Ikiform als Webhook-Ziel, wenn du jede Einsendung mit Spam-Status gespeichert, gegen deine Felder validiert, ans Team gemailt und an mehrere signierte Webhooks weitergeleitet haben willst. Da Framer von seinen Servern sendet, lockere entweder die Origin-Prüfungen des Formulars oder setze ein kleines Relay dazwischen, das Framers Signatur prüft.

Was auf Framer anders ist

Framer ist der einzige Builder in dieser Anleitung ohne Limit, dem man entkommen müsste. Seine Formulare können an E-Mail, Google Sheets oder jeden Webhook senden, und Framer hat eingebauten Spamschutz, der verdächtige Einsendungen markiert oder blockiert. Seine Webhooks sind signiert: Jeder Request trägt einen Header Framer-Signature, ein HMAC-SHA256 über Body und Einsendungs-ID, und einen Framer-Webhook-Submission-Id. Framer wiederholt bis zu fünfmal, wenn dein Endpunkt nicht mit 2xx antwortet, und folgt keinen Weiterleitungen.

Sei also ehrlich, warum du ein Formular-Backend ergänzen würdest. Das deckt jede Seite ab:

Du willstNur FramerMit Ikiform als Webhook
Einsendungen in einem Posteingang oder einer TabelleJaJa, plus das Dashboard
Ein durchsuchbares Archiv mit markiertem Spam und CSV-ExportDein Posteingang oder deine Tabelle ist das ArchivJa
Serverseitige Prüfung von Typen, Längen und OptionenNeinJa, mit einer Felddefinition
Benachrichtigungen an mehrere Personen, wobei Antworten an den Absender gehenE-Mail-ZielBis zu 10 Empfänger, mit Reply-to
Mehrere Tools aus einem Formular gespeist, jedes mit WiederholungenEin Webhook pro hinzugefügtem ZielBis zu 3 signierte Endpunkte, wiederholt, mit Zustellungsprotokoll
Dasselbe Backend für Framer, Webflow und selbst programmierte SitesNeinJa

Reicht dir die erste Zeile, bleib bei den Zielen von Framer. Der Rest dieser Seite ist für den Fall, dass sie nicht reicht.

Der Haken: Framer sendet von seinen Servern

Ein Webhook-Ziel heißt, dass die Server von Framer den Post senden, nicht der Browser des Besuchers. Auf der Seite von Ikiform ändern sich drei Dinge:

  • Kein Origin. Server-Posts tragen keinen Origin-Header, also wird bei gesetzten Allowed domains jeder Post so behandelt, als käme er von der falschen Site.
  • Geteilte IP-Adressen. Ikiform zählt Rate-Limits pro IP-Adresse, und jeder Post kommt von wenigen Framer-Adressen statt von denen deiner Besucher.
  • Kein Honeypot. Framer sendet die Inputs, die du gebaut hast, und sonst nichts, und sein Formular kann Ikiforms Menschlichkeitsprüfung nicht ausführen.

Du kannst das hinnehmen oder es mit einem Relay beheben. Beide Wege stehen unten.

Ein Framer-Formular, das direkt und über ein signiertes Relay an Ikiform sendetDirekter WebhookSigniertes RelayFramer-FormularFramer-ServerJSON, keinOriginIkiformDomainsleer,Relaxed-LimitFramer-FormularFramer-Serversigniertjeden PostdeinWorkerprüftFramer-SignatureIkiformSecret Key,IdempotencyKey401,verworfenabsendenWebhookabsendenWebhookweiterleitenfalsche SignaturEin Framer-Formular, das direkt und über ein signiertes Relay an Ikiform sendetDirekter WebhookSigniertes RelayFramer-FormularFramer-ServerJSON, kein OriginIkiformDomains leer,Relaxed-LimitFramer-FormularFramer-Serversigniert jeden Postdein WorkerprüftFramer-SignatureIkiformSecret Key,Idempotency Key401,verworfenabsendenWebhookabsendenWebhookweiterleitenfalsche Signatur
  1. Direkt: Ein Besucher sendet das Framer-Formular ab, die Server von Framer senden es als JSON ohne Origin-Header, und Ikiform nimmt es mit leeren Allowed domains und einem Relaxed-Rate-Limit an.
  2. Relay: Die Server von Framer signieren jeden Post mit deinem Secret und senden ihn an einen Worker, den du deployst.
  3. Der Worker berechnet die Signatur von Framer neu. Ein Post ohne gültige Signatur bekommt 401 und geht nicht weiter.
  4. Ein gültiger Post wird mit deinem Secret Key und der Einsendungs-ID von Framer als Idempotency Key an Ikiform weitergeleitet, sodass eine Wiederholung von Framer nie eine zweite Kopie speichert.
Das Relay besteht aus einem Dutzend Zeilen echter Logik. Dafür kann das Formular strenge erlaubte Domains behalten, weil nur dein Worker den Secret Key hat.

Option 1: Ikiform als Webhook

  1. Gib in Framer jedem Input einen kleingeschriebenen Namen: name, work_email, company, team_size. Framer nutzt die Namen als JSON-Keys.
  2. Wähle das Formular und neben Send To dann Add… > Webhook. Füge deinen Endpunkt ein. Lass das Secret leer; Ikiform prüft Framer-Signature nicht.
  3. Lass in den Einstellungen des Ikiform-Formulars Allowed domains leer, lass Spam check aus und setze Rate limit auf Relaxed.
  4. Veröffentliche. Framer sendet Webhooks nur von der veröffentlichten Site.
Webhook-URL
https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx

Ikiform antwortet mit 201, sobald die Einsendung gespeichert ist, was Framer als zugestellt zählt. Ein 422 validation_failed scheitert bei allen fünf Wiederholungen gleich, also korrigiere die Input-Namen, statt zu warten. Der Spam-Score läuft trotzdem bei jedem Post: Links, Spam-Phrasen, Wegwerfadressen und Wiederholungen.

Option 2: ein signiertes Relay

Deploye einen Cloudflare Worker, der die Signatur von Framer prüft und gültige Posts mit deinem Secret Key weiterleitet. Dann kann das Ikiform-Formular deine Framer-Domain unter Allowed domains mit Reject listen, sodass alles abgewiesen wird, was weder ein Browser auf deiner Site noch dein Worker ist.

src/index.ts
interface Env {
  FRAMER_SECRET: string;
  IKIFORM_SECRET_KEY: string;
}

const ENDPOINT = "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx";
const encoder = new TextEncoder();

async function signatureFor(secret: string, body: ArrayBuffer, submissionId: string) {
  const key = await crypto.subtle.importKey(
    "raw",
    encoder.encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"],
  );
  const id = encoder.encode(submissionId);
  const message = new Uint8Array(body.byteLength + id.byteLength);
  message.set(new Uint8Array(body), 0);
  message.set(id, body.byteLength);
  const digest = new Uint8Array(await crypto.subtle.sign("HMAC", key, message));
  return `sha256=${[...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
}

function sameText(a: string, b: string) {
  const left = encoder.encode(a);
  const right = encoder.encode(b);
  return left.byteLength === right.byteLength && crypto.subtle.timingSafeEqual(left, right);
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== "POST") return new Response(null, { status: 405 });
    const signature = request.headers.get("framer-signature") ?? "";
    const submissionId = request.headers.get("framer-webhook-submission-id") ?? "";
    const body = await request.arrayBuffer();
    if (!submissionId || !sameText(signature, await signatureFor(env.FRAMER_SECRET, body, submissionId))) {
      return new Response("Bad signature", { status: 401 });
    }

    const response = await fetch(ENDPOINT, {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Bearer ${env.IKIFORM_SECRET_KEY}`,
        "Idempotency-Key": submissionId,
      },
      body,
    });
    if (response.ok) return new Response(null, { status: 204 });
    return new Response(await response.text(), { status: response.status });
  },
};

Deployen

Terminal
npx wrangler secret put FRAMER_SECRET
npx wrangler secret put IKIFORM_SECRET_KEY
npx wrangler deploy
  • FRAMER_SECRET ist das Secret, das du am Webhook-Ziel von Framer setzt. Framer verlangt mindestens 32 Zeichen und zeigt es nicht erneut an, also erzeuge es, speichere es und füge es an beiden Stellen ein.
  • IKIFORM_SECRET_KEY ist ein geheimer API-Key aus dem Ikiform-Dashboard. Leg ihn nie in Framer ab.
  • Die Signatur ist sha256= plus das Hex-HMAC über den rohen Body, gefolgt von der Einsendungs-ID. Lies den Body einmal als Bytes und nutze genau diese Bytes für die Prüfung und die Weiterleitung.
  • Idempotency-Key sorgt dafür, dass eine Wiederholung von Framer die erste Antwort von Ikiform wiedergibt, statt ein Duplikat zu speichern. Keys werden 24 Stunden aufbewahrt.
  • Der Worker reicht den Status von Ikiform durch. Framer wiederholt jede Antwort außerhalb von 2xx bis zu fünfmal, also bekommt ein 429 oder 5xx eine weitere Chance, während ein 422 jedes Mal gleich scheitert, bis du die Input-Namen korrigierst.

Richte den Webhook von Framer dann auf die URL des Workers statt auf den Endpunkt, mit eingetragenem Secret.

Jeder Post kommt jetzt von deinem Worker. Die serverseitigen Versionen senden mit einem Secret Key, also überspringt Ikiform Allowed domains und seine Menschlichkeitsprüfung. Jeder Post kommt dann von der IP deines Servers, und das Standard-Limit von 5 pro Minute pro Besucher zählt alle deine Besucher zusammen. Wähle für ein Formular, das du über einen Server sendest, das Rate-Limit Relaxed oder Custom und halte IKIFORM_SECRET_KEY in Umgebungsvariablen, die nur der Server sieht.

Option 3: das Formular von Framer weglassen

Eine Code-Komponente rendert ein echtes Formular und sendet aus dem Browser des Besuchers, sodass Origin-Prüfungen, der Honeypot und Rate-Limits pro Besucher wie auf jeder Site funktionieren. Du verlierst für dieses Formular die visuelle Formularbearbeitung von Framer. Die Framer-Anleitung der Doku enthält die Komponente.

Prüfen, dass es auf Framer funktioniert

  1. Veröffentliche die Framer-Site und sende das Formular dort ab, nicht in der Vorschau des Editors.
  2. Öffne Submissions im Dashboard. Die neue Einsendung ist da, mit jedem Feld, das du gesendet hast.
  3. Prüfe, dass die Feldnamen genau zu deinen Input-Namen passen, ohne von Framer ergänzte Keys.
  4. Für das Relay: Sende einen Request mit einer erfundenen Framer-Signature an den Worker und prüfe, dass er mit 401 antwortet.
  5. Für das Relay: In den Details der Einsendung ist der Origin leer und der Status received, nicht spam.
  6. Prüfe das Postfach jedes Benachrichtigungsempfängers. Jeder bekommt eine E-Mail mit den Feldern, und Reply geht an den Absender, wenn das Formular ein email-Feld hat.
  7. Wenn du einen Webhook hinzugefügt hast, öffne den Endpunkt unter Webhooks. Die Zustellung zeigt succeeded mit der Antwort deines Servers.

Stolperfallen

ProblemWas zu tun ist
Jeder Webhook-Post liegt mit origin_mismatch im SpamAllowed domains ist gesetzt, während Framer direkt sendet. Leere die Liste oder setze das Relay dazwischen.
Der Worker antwortet auf echte Posts mit 401Das Secret unterscheidet sich zwischen Framer und Worker, oder etwas hat den Body als Text gelesen und verändert. Füge das Secret erneut ein und hashe die rohen Bytes.
429 rate_limited während eines LaunchesAlle Posts teilen sich die Adressen von Framer oder Cloudflare. Wähle Relaxed oder ein Custom-Limit.
422 validation_failedDer Post passt nicht zur Felddefinition des Formulars. error.fields nennt jedes Feld und den Grund. Gleiche den name des Eingabefelds an die Feld-ID an.
403 challenge_requiredDie Spamprüfung des Formulars steht auf Ikiform, und ein fetch-Post hatte kein Token. Hol dir vor jedem Absenden eins mit challenge(formId) aus @ikiform/sdk oder schalte die Prüfung aus.

Fragen

Hängst du bei einem Schritt fest oder willst eine Anleitung für deinen Fall? Schreib an hello@ikiform.com.