# So fügst du einer Framer-Site eine Warteliste hinzu

> Zwei Wege, auf Framer Wartelisten-Anmeldungen zu sammeln: Framers Formular mit einem Webhook an Ikiform oder eine Code-Komponente, die Limits pro Besucher und Spamprüfungen behält.

Veröffentlicht am 2026-10-25 · https://ikiform.com/de/guides/waitlist/framer

Framer-Formulare können nicht an eine externe URL senden, aber jede Einsendung an einen Webhook schicken, der No-Code-Weg ist also Framers Formular mit deinem Ikiform-Endpunkt als Webhook. Für einen öffentlichen Launch nimm stattdessen eine kleine Code-Komponente: Der Browser sendet direkt, Ikiforms Rate-Limit pro Besucher, erlaubte Domains und der Honeypot funktionieren also weiter.

Auf Framer entstehen viele Pre-Launch-Seiten, weil die Seite selbst einen Nachmittag dauert. Beim Wartelisten-Formular wird es weniger offensichtlich. Framers Formularblock kann seine `action` nicht ändern, du wählst also zwischen zwei Wegen, die sich unterschiedlich verhalten, sobald echter Traffic kommt.

## Was auf Framer anders ist

- **Keine eigene action.** Ein Framer-Formular sendet an Framers Ziele: seinen eigenen Speicher, E-Mail oder einen **Webhook**. Ein Webhook ist der einzige Weg, ohne Code ein externes Backend zu erreichen.
- **Webhooks kommen von Framers Servern, nicht vom Besucher.** Ikiform sieht Framers IP-Adressen und keinen `Origin`-Header. Das schaltet drei Schutzmechanismen ab: erlaubte Domains, das Rate-Limit pro Besucher und die Menschlichkeitsprüfung.
- **Wiederholungen.** Framer behandelt alles außer einem `2xx` als Fehler und versucht es bis zu fünfmal erneut. Es folgt keinen Weiterleitungen. Ikiform beantwortet einen JSON-Post mit `201`, eine gespeicherte Anmeldung ist also beim ersten Mal ein Erfolg.
- **Code-Komponenten sind echtes React.** Sie laufen auf der veröffentlichten Site im Browser des Besuchers, ein Formular darin sendet also wie jede andere Webseite.

Diagramm: Zwei Wege, wie eine Framer-Wartelisten-Anmeldung Ikiform erreicht

1. Beim Webhook-Weg sendet der Besucher Framers Formular ab, und Framers Server leiten die Felder als JSON an Ikiform weiter.
2. Ikiform sieht Framers Server als Absender, kann also Besucher nicht unterscheiden oder prüfen, auf welcher Site das Formular steht.
3. Bei der Code-Komponente sendet der Browser des Besuchers direkt an Ikiform.
4. Ikiform sieht die IP jedes Besuchers und deine Domain, also greift jeder Schutz.

Beide Wege speichern dieselbe Anmeldung. Sie unterscheiden sich darin, wen Ikiform für den Absender hält.

## Einen Weg wählen

|   | Framer-Formular mit Webhook | Code-Komponente |
| --- | --- | --- |
| Einrichtung | Zwei Felder in der rechten Seitenleiste | Eine Code-Datei, einmal eingefügt |
| Gestaltung | Framers Formular-Styles und -Zustände | Inline-Styles, die du bearbeitest, oder eigene Klassen |
| Rate-Limit pro Besucher | Nein. Alle Posts teilen sich Framers IPs | Ja, je 5 pro Minute |
| Erlaubte Domains | Leer lassen | Auf deine Framer- und eigenen Domains setzen |
| Honeypot | Nein | Ja |
| Menschlichkeitsprüfung | Aus | Aus, weil sie mit `fetch` sendet |
| Quellen-Tracking | Keine | `?ref=` aus der URL oder ein Standardwert pro Instanz |

Der Webhook-Weg ist in Ordnung für eine Seite, die du mit ein paar Hundert Leuten teilst. Wenn du die Seite gleich irgendwo Großes postest, nimm die Code-Komponente: Einen Schub Bot-Anmeldungen über Framers Server kann nur die Spam-Bewertung stoppen.

## Weg 1: Framers Formular mit einem Webhook

1. Füge der Seite ein Formular mit einem **Email**-Eingabefeld hinzu. Setze in den Einstellungen des Felds seinen Namen auf `email`. Framer sendet den Namen jedes Felds als JSON-Schlüssel, und Ikiform nutzt `email` für Reply-to und die Prüfung auf Wegwerf-Domains.
2. Wähle das Formular aus. Wähle neben **Send To** **Add…** › **Webhook** und füge deinen Endpunkt `https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx` ein. Das Signing-Secret ist optional; Ikiform prüft Framers Signatur nicht.
3. Lass in Ikiform **Allowed domains** leer und die Spamprüfung aus. Ist eines davon aktiv, wird jeder Framer-Post markiert oder abgelehnt, weil er keinen Origin trägt und keine Browser-Prüfung ausführen kann.
4. Setze das [Rate-Limit](https://ikiform.com/de/docs/rate-limits.md) auf Relaxed, oder am Launch-Tag auf Custom. Standard zählt alle Posts von Framer als einen Besucher, und das ganze Formular ist auf 300 pro Minute begrenzt.
5. Gestalte den Erfolgszustand des Formulars in Framer. Er erscheint, wenn Ikiform mit `201` antwortet.

> Ein Post, den Ikiform ablehnt, etwa eine fehlende E-Mail bei vorhandener Felddefinition, scheitert bei allen fünf Wiederholungen von Framer gleich. Prüfe die Zustellung in Framer, wenn keine Anmeldungen mehr ankommen.

## Weg 2: eine Code-Komponente

Öffne im Panel **Assets** den Bereich **Code**, erstelle eine Code-Datei namens `WaitlistSignup` und füge das hier ein. Zieh sie auf die Seite und setze im Eigenschaften-Panel **Form ID**.

`WaitlistSignup.tsx`:

```tsx
import { addPropertyControls, ControlType } from "framer";
import { type CSSProperties, type FormEvent, useRef, useState } from "react";

const box: CSSProperties = {
  font: "inherit",
  padding: "12px 14px",
  borderRadius: 10,
  border: "1px solid rgba(0, 0, 0, 0.15)",
};

interface Props {
  formId: string;
  buttonLabel: string;
  joinedText: string;
  source: string;
}

export default function WaitlistSignup({ formId, buttonLabel, joinedText, source }: Props) {
  const [status, setStatus] = useState<"idle" | "sending" | "joined">("idle");
  const [error, setError] = useState("");
  const key = useRef("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const body = new FormData(event.currentTarget);
    const ref = new URLSearchParams(window.location.search).get("ref");
    body.set("ref", (ref || source).slice(0, 40));
    key.current ||= crypto.randomUUID();
    setStatus("sending");
    setError("");
    try {
      const response = await fetch(`https://ikiform.com/f/${formId}`, {
        method: "POST",
        headers: { Accept: "application/json", "Idempotency-Key": key.current },
        body,
      });
      const result = await response.json();
      if (result.ok) return setStatus("joined");
      setError(result.error.fields?.email?.[0] ?? result.error.message);
    } catch {
      setError("Couldn't reach the server. Try again.");
    }
    setStatus("idle");
  }

  if (status === "joined") return <p role="status" style={{ margin: 0 }}>{joinedText}</p>;

  return (
    <form onSubmit={onSubmit} style={{ display: "flex", flexWrap: "wrap", gap: 8, width: "100%" }}>
      <input
        name="email"
        type="email"
        placeholder="you@company.com"
        aria-label="Email"
        autoComplete="email"
        required
        style={{ ...box, flex: "1 1 220px" }}
      />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      <button type="submit" disabled={status === "sending"} style={{ ...box, cursor: "pointer" }}>
        {status === "sending" ? "Joining" : buttonLabel}
      </button>
      {error ? <p role="alert" style={{ flexBasis: "100%", margin: 0 }}>{error}</p> : null}
    </form>
  );
}

addPropertyControls(WaitlistSignup, {
  formId: { type: ControlType.String, title: "Form ID", defaultValue: "frm_xxxxxxxxxxxxxxxx" },
  buttonLabel: { type: ControlType.String, title: "Button", defaultValue: "Join the waitlist" },
  joinedText: { type: ControlType.String, title: "Joined text", defaultValue: "You're on the list." },
  source: { type: ControlType.String, title: "Source", defaultValue: "framer" },
});
```

- Das Eigenschaften-Panel bietet die Button-Beschriftung, den Text nach dem Eintragen und eine Standard-**Source**. Setz eine Instanz mit `hero` in den Hero-Bereich und eine weitere mit `footer` in den Footer, und du siehst, welche besser konvertiert.
- Ein `?ref=` in der Seiten-URL gewinnt gegen den Standardwert, Kampagnenlinks markieren sich also selbst.
- Der Idempotenzschlüssel wird beim ersten Absenden erzeugt und bei Wiederholungen aus demselben Besuch wiederverwendet, ein Doppeltipp am Handy speichert also eine Anmeldung.
- `window` wird nur im Submit-Handler gelesen. Framer rendert Komponenten zuerst auf dem Server, und ein Zugriff auf `window` beim Rendern würde das kaputt machen.

### Ikiform-Einstellungen für die Komponente

Füge diese im Tab **Fields** des Formulars hinzu, damit die E-Mail Pflicht ist und `ref` erlaubt ist:

`Felder`:

```json
{
  "version": 1,
  "title": "Waitlist",
  "fields": [
    { "id": "email", "type": "email", "label": "Email", "required": true },
    { "id": "ref", "type": "text", "label": "Source", "validation": { "max_length": 40 } }
  ]
}
```

- Füge unter **Allowed domains** `your-project.framer.app` und deine eigene Domain hinzu. Füge nie `framer.app` allein hinzu; jede Framer-Site würde passen.
- Lass die Spamprüfung aus. Die Komponente sendet mit `fetch` und holt kein Token, ein Formular mit aktiver Prüfung antwortet also mit `403 challenge_required`.
- Behalte die Standard-Rate-Limits. Jeder Besucher wird jetzt einzeln gezählt.

## Prüfen, ob es funktioniert

1. Veröffentliche die Site. Webhooks und Code-Komponenten senden nur von der veröffentlichten Site, nicht aus der Vorschau im Editor.
2. Sende das Formular auf deiner `framer.app`-Domain ab und noch einmal auf deiner eigenen Domain.
3. Öffne **Submissions** im Dashboard. Die neue Einsendung ist da, mit jedem Feld, das du gesendet hast.
4. Öffne für die Komponente die Seite mit `?ref=test` und prüfe, dass die Anmeldung `ref` als `test` zeigt.
5. 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.
6. Nichts angekommen? Stell den Statusfilter im Dashboard auf **Spam**. Spam bekommt dieselbe Erfolgsantwort wie eine echte Einsendung.

## Stolperfallen

| Problem | Was zu tun ist |
| --- | --- |
| Jede Webhook-Anmeldung liegt mit `origin_mismatch` im Spam | **Allowed domains** ist gesetzt, und Framers Server-Posts haben keinen Origin. Leere es für dieses Formular oder wechsle zur Code-Komponente. |
| Framer meldet fehlgeschlagene Webhook-Zustellungen | Ikiform hat den Post abgelehnt: `403 challenge_required` bei aktiver Spamprüfung oder `422`, weil ein Feldname nicht `email` ist. Beheb die Ursache; Wiederholungen scheitern genauso. |
| `429 rate_limited` | Ein Besucher hat mehr gesendet, als das Rate-Limit des Formulars erlaubt. Standard erlaubt 5 pro Minute und Besucher. Warte `Retry-After` ab oder wähle Relaxed oder Custom für Formulare, die von einer gemeinsamen IP aus senden. |
| Die Komponente zeigt `Unknown field "ref"` | Die Felddefinition listet `ref` nicht auf. Füge es wie in der Definition oben hinzu. |
| `403 origin_not_allowed`, oder jeder Post landet im Spam | Die Domain der Seite steht nicht unter **Allowed domains** des Formulars. Füge sie hinzu, plus `localhost` für lokale Tests. Subdomains einer eingetragenen Domain sind erlaubt. |

Die vollständige Framer-Einrichtung, einschließlich der Kontaktformular-Version, steht in der [Framer-Anleitung in der Doku](https://ikiform.com/de/docs/guides/framer.md). Export der Liste und Versand der Launch-E-Mail stehen in der [Hauptanleitung](https://ikiform.com/de/guides/waitlist.md#launch-day).

## Fragen

### Kann ein Framer-Formular an eine externe URL senden?

Nicht als Form-Action. Framer-Formulare senden an Framers eigene Ziele, und eines davon ist ein Webhook an eine beliebige https-URL. Nutze den für Ikiform oder rendere mit einer Code-Komponente dein eigenes Formular.

### Warum werden meine Framer-Wartelisten-Anmeldungen als Spam markiert?

Beim Webhook-Weg sendet Framer von seinen Servern ohne Origin-Header, ein Formular mit gesetzten Allowed domains markiert also jeden Post als origin_mismatch. Lass Allowed domains bei Webhook-Formularen leer.

### Zählen Framer-Webhook-Anmeldungen zu Framers Formularlimits?

Laut Framer sind Formulareinsendungen unbegrenzt, Webhook-Anmeldungen stoßen also an keine Framer-Grenze. Der Weg über die Code-Komponente nutzt Framers Formulare gar nicht.

### Ist Ikiform kostenlos?

Nein, es gibt keinen kostenlosen Tarif. Testen ist kostenlos: Du kannst 50 Testeinsendungen pro Monat von localhost senden, bevor du zahlst. Für den Livebetrieb startet Pro bei $6/Monat für 500 Einsendungen, oder du zahlst einmalig 10.000 Einsendungen für $49 oder 50.000 Einsendungen für $149, mit Guthaben, das nie verfällt.

## Weitere Anleitungen

- [Warteliste für ein neues SaaS](https://ikiform.com/de/guides/waitlist.md): Baue eine Pre-Launch-Warteliste auf deiner eigenen Landingpage: das Formular, Spamschutz, Duplikate, Bestätigungs-E-Mails und ein sauberer Export am Launch-Tag.
- [Warteliste auf Webflow](https://ikiform.com/de/guides/waitlist/webflow.md): Richte einen Webflow Form Block auf Ikiform aus, markiere Anmeldungen mit einem versteckten Feld nach Quelle und behalte mit einem kurzen Skript Webflows Erfolgszustand für jedes Formular.
- [Warteliste auf Carrd](https://ikiform.com/de/guides/waitlist/carrd.md): Sammle Wartelisten-Anmeldungen auf einer Carrd-Seite: ein Send-to-URL-Formular auf Pro Plus mit UTM-Tracking oder ein eingebettetes HTML-Formular mit Honeypot auf Pro Standard.
- [Warteliste in reinem HTML](https://ikiform.com/de/guides/waitlist/html.md): Eine komplette Wartelisten-Seite in reinem HTML ohne Build, die ohne JavaScript funktioniert, nach dem Laden ein Danke inline zeigt und bei Bedarf zurückfällt.
- [Launch-Liste für Product Hunt](https://ikiform.com/de/guides/waitlist/product-hunt.md): Sammle Wochen vor Product Hunt eine Launch-Liste, markiere jede Anmeldung nach Kanal und sende die Launch-E-Mail in zwei Wellen, abgestimmt auf den Reset nach Pacific Time.
- [Beta-Anmeldung mit Auswahl](https://ikiform.com/de/guides/waitlist/beta-signup.md): Ein Beta-Bewerbungsformular mit Auswahlfragen, die der Server durchsetzt, und ein Skript, das Bewerber bewertet und jede Kohorte aus der Liste auswählt.
