# So funktioniert es

> Jede Prüfung, die eine Einsendung durchläuft, in Reihenfolge, und was jede davon zurückgeben kann.

Jeder Post an `/f/<formId>` durchläuft dieselbe Pipeline. Günstige Prüfungen laufen zuerst, sodass eine Flut oder ein unbekanntes Formular fast nichts kostet. Die erste fehlschlagende Prüfung bestimmt die Antwort.

Die Pipeline, in Reihenfolge:

1. IP-Flutschutz: höchstens 600 Posts pro Minute von einer IP über alle Formulare hinweg, bevor das Formular nachgeschlagen wird. Schlägt mit `429 rate_limited` fehl.
2. Formularsuche: unbekannte, gelöschte und deaktivierte Formulare schlagen mit `404 not_found` fehl.
3. Rate-Limits des Formulars: das Preset des Formulars, gezählt pro Besucher-IP und pro Formular. Schlägt mit `429 rate_limited` fehl.
4. Pausierte Formulare schlagen mit `403 form_paused` fehl.
5. Body-Parsing: urlencoded, multipart oder JSON, höchstens 256 KB. Schlägt mit `415`, `400 invalid_body`, `413 payload_too_large`, `422 too_deep` oder `422 files_not_supported` fehl.
6. API-Schlüssel, wenn ein `Authorization`-Header gesendet wird. Schlägt mit `403 forbidden` oder `403 origin_not_allowed` fehl.
7. Erlaubte Domains: ein Post von einer anderen Website wird als Spam gespeichert oder schlägt mit `403 origin_not_allowed` fehl.
8. Menschlichkeitsprüfung: schlägt mit `403 challenge_required` oder `403 captcha_failed` fehl. Formular-Posts aus dem Browser bekommen stattdessen eine Prüfseite.
9. Feldvalidierung: schlägt mit `422 validation_failed` fehl.
10. Spam-Bewertung: ein Score von 5 oder mehr wird als Spam gespeichert, aber trotzdem als Erfolg beantwortet.
11. Idempotenz: ein wiederholter Schlüssel spielt die erste Antwort erneut ab oder schlägt mit `409` oder `422` fehl.
12. Monatliche Nutzung: weit über dem Limit schlägt mit `429 quota_exceeded` fehl.
13. Speichern: `201` mit der Einsendungs-ID oder eine `303`-Weiterleitung bei Browser-Posts. Webhooks, Benachrichtigungs-E-Mails und das Live-Update im Dashboard folgen.

## Was jede Stufe macht

### Rate-Limits vor allem anderen

Rate-Limits greifen, bevor der Body gelesen wird. Der Flutschutz (600 pro Minute pro IP) läuft sogar vor der Formularsuche, sodass eine Flut nie die Datenbank erreicht. Die eigenen Regeln des Formulars greifen direkt nach der Suche. Abgelehnte Anfragen verbrauchen kein Kontingent. Genaue Zahlen findest du unter [Rate-Limits](https://ikiform.com/de/docs/rate-limits.md).

### Parsing

Der Body wird als urlencoded, multipart oder JSON geparst. Text wird als UTF-8 dekodiert und nach NFC normalisiert, CRLF wird zu LF und Steuerzeichen werden entfernt. Namen, die mit `_` beginnen, sind reservierte Anweisungen wie `_redirect` und `_subject` und werden nie als Antworten gespeichert. Siehe [HTML-Formulare](https://ikiform.com/de/docs/html.md#reserved-fields).

### Wer es gesendet hat

Ein `Authorization: Bearer`-Header ist optional. Ein Secret Key (`ik_sk_`) mit dem Scope `submissions:write` überspringt die Prüfung der erlaubten Domains und die Ikiform-Menschlichkeitsprüfung. Ein Publishable Key (`ik_pub_`) funktioniert nur von den Domains, die beim Schlüssel eingetragen sind. Ohne Schlüssel wird der Post anhand seines `Origin` beurteilt.

### Spam

Spam ist nie ein Fehler. Ein Post, der die Domain-Richtlinie im Spam-Modus nicht erfüllt oder einen Score von 5 oder mehr erreicht, wird mit Status `spam` gespeichert und bekommt dieselbe Erfolgsantwort wie ein echter, sodass Bots nichts lernen. Spam löst keine Webhooks oder E-Mails aus und zählt nicht zur Nutzung. Siehe [Spamschutz](https://ikiform.com/de/docs/spam.md).

### Speichern und zustellen

Die Einsendung, ihre Webhook-Zustellungen und ihre Benachrichtigungs-E-Mail werden in einem Datenbank-Batch geschrieben und dann an eine Queue übergeben. Webhook-Zustellung und E-Mail verlangsamen nie die Antwort, und eine Einsendung wird nie ohne ihre Zustellungen gespeichert.

## JSON oder eine Weiterleitung

Derselbe Endpunkt antwortet Browsern mit Seiten und Code mit JSON. Ikiform entscheidet pro Anfrage:

| Anfrage | Antwort |
| --- | --- |
| `Accept` enthält `application/json` | JSON |
| Der Body ist JSON | JSON |
| `Sec-Fetch-Mode: navigate` (ein normaler Formular-Post) | Weiterleitung oder gehostete Seite |
| Jeder andere `Sec-Fetch-Mode`, etwa `cors` von `fetch` | JSON |
| Kein `Sec-Fetch-Mode`, und `Accept` enthält `text/html` | Weiterleitung oder gehostete Seite |
| Alles andere, etwa curl | JSON |

Im JSON-Modus ist Erfolg `201 { "ok": true, "id": "sub_..." }`. Im Seitenmodus ist Erfolg eine `303`-Weiterleitung, und Fehler zeigen eine gehostete Fehlerseite, die Feldprobleme nach Label auflistet.

## Nachdem eine Einsendung gespeichert ist

| Was | Wann | Details |
| --- | --- | --- |
| Webhooks | Sekunden später, erneute Versuche über etwa 3 Tage | [Webhooks](https://ikiform.com/de/docs/webhooks.md) |
| Benachrichtigungs-E-Mail | Sekunden später, wenn Benachrichtigungen für das Formular aktiv sind | Begrenzt auf 50 E-Mails pro Formular und Stunde (10 bei Workspaces, die jünger als 7 Tage sind), danach eine stündliche Zusammenfassung |
| Dashboard | Sofort, live | Spam erscheint unter dem Spamfilter |
| REST API | Sofort | [REST API](https://ikiform.com/de/docs/api.md) |

> Formulare, deren Einsendungen Feldnamen enthalten, die nach Passwort, Karte oder Seed-Phrase aussehen, werden markiert und ihre Benachrichtigungs-E-Mails zur Prüfung zurückgehalten. Sammle keine Zugangsdaten mit einem Formular-Backend.

## IDs

| ID | Sieht aus wie | Hinweise |
| --- | --- | --- |
| Formular | `frm_xxxxxxxxxxxxxxxx` | `frm_` plus 16 zufällige Zeichen. Öffentlich: Sie steht in der Action-URL deines Formulars. |
| Einsendung | `sub_01k7v2c9t3e8m5q4x6z0a1b2c3` | `sub_` plus 26 Zeichen, zeitlich sortiert. |
| Webhook-Zustellung | `dlv_...` | Wird als `webhook-id` gesendet; bleibt bei erneuten Versuchen gleich. |
| Anfrage | `req_...` | Jede Antwort hat einen `X-Request-Id`-Header. Gib ihn an, wenn du den Support kontaktierst. |

**201** (`JSON-Erfolg`)

```json
{ "ok": true, "id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3" }
```

**303** (`Browser-Erfolg`)

```http
HTTP/1.1 303 See Other
Location: https://example.com/thanks
X-Request-Id: req_01k7v2c9t3e8m5q4x6z0a1b2c3
```
