# Formular einbetten und vorausfüllen

Formulare lassen sich auf Ihrer eigenen Website zeigen und mit Werten aus Ihrem System vorausfüllen – zum Beispiel Name oder Kundennummer aus Ihrem CRM. Beides geht in allen Stufen.

Maßgeblich für Ihre Geschäftslogik bleibt der **Webhook** (`submission.submitted`, `submission.completed`) bzw. `GET /v1/events`. Die Nachrichten der eingebetteten Seite sind Hinweise für Ihre Oberfläche, keine Bestätigung.

## 1. Erlaubte Websites festlegen

Nur Websites auf der Liste **dieses Formulars** dürfen es einbetten. Die Liste ist leer, bis Sie sie füllen – leer heißt: Einbettung aus.

```http
PATCH /v1/forms/{id}
Authorization: Bearer qgw_live_…
Idempotency-Key: 8c2e…
Content-Type: application/json

{ "settings": { "embed": { "allowedOrigins": ["https://app.example.com"] } } }
```

Regeln: `https://host[:port]` ohne Pfad und ohne `*`, höchstens 10 Einträge. Im Dashboard steht die Liste im Reiter „Teilen“.

## 2. Einbettungscode abrufen

```http
GET /v1/forms/{id}/embed
Authorization: Bearer qgw_live_…
```

```json
{
  "object": "form_embed",
  "publicUrl": "https://sign.example.com/f/antrag-anwohnerparkausweis-k7m2p9qa",
  "iframeHtml": "<iframe src=\"https://sign.example.com/f/antrag-anwohnerparkausweis-k7m2p9qa?embed=1\" title=\"Antrag auf Anwohnerparkausweis\" style=\"width:100%;border:0;min-height:480px\" loading=\"lazy\" data-form-embed=\"v1\"></iframe>\n<script src=\"https://sign.example.com/embed/v1/forms.js\" async></script>",
  "scriptUrl": "https://sign.example.com/embed/v1/forms.js",
  "qrCodeSvg": "<svg …>",
  "allowedOrigins": ["https://app.example.com"]
}
```

Fügen Sie `iframeHtml` in Ihre Seite ein. Das Skript `forms.js` (ohne Abhängigkeiten, ohne Cookies) passt die Höhe des Rahmens automatisch an; ohne Skript funktioniert der Rahmen mit fester Höhe und eigenem Scrollen.

So ist die Einbettung geschützt:

- Der Browser zeigt die Seite nur in einem Rahmen auf einer erlaubten Website (Kopfzeile `Content-Security-Policy: frame-ancestors`, bis zu 60 Sekunden zwischengespeichert). Ohne `?embed=1` darf die Seite nie in einem Rahmen erscheinen.
- Meldet das Skript eine andere Website, zeigt die Seite den Hinweis „Dieses Formular darf auf dieser Website nicht eingebettet werden“ mit einem Link zum direkten Öffnen; der Start antwortet 403 / 9038 `EMBED_ORIGIN_NOT_ALLOWED`.

## 3. Nachrichten der eingebetteten Seite

Die Seite sendet Nachrichten nur an die geprüfte Adresse Ihrer Website (nie an `*`) und nie mit Werten, Namen, Adressen oder Schlüsseln:

```json
{ "source": "onesign", "v": 1, "type": "submission.submitted", "formId": "k7m2p9qa", "number": 133, "at": "2026-10-03T12:00:00.000Z" }
```

| `type` | Wann | Felder |
|---|---|---|
| `form.ready` | Seite geladen | – |
| `form.resize` | Höhe geändert | `height` |
| `form.page` | Schritt gewechselt (das Skript scrollt den Rahmen nach oben) | `index` |
| `submission.submitted` | abgesendet | `number` |
| `form.closed` | Formular geschlossen | – |
| `error` | Start abgelehnt | `code` (z. B. 9038) |

`formId` ist die öffentliche Kennung aus dem Link. Das Skript gibt jede Nachricht zusätzlich als DOM-Ereignis `formembed` am `iframe` weiter:

```js
document.querySelector('iframe[data-form-embed]').addEventListener('formembed', (event) => {
  if (event.detail.type === 'submission.submitted') zeigeDanke(event.detail.number);
});
```

## 4. Links mit Vorbelegung

```http
POST /v1/forms/{id}/prefill-links
Authorization: Bearer qgw_live_…
Idempotency-Key: 41a7…
Content-Type: application/json

{
  "values": { "vorname": "Anna", "nachname": "Weber", "kundennummer": "K-1001" },
  "lockedKeys": ["kundennummer"],
  "expiresAt": "2026-10-31T23:00:00+01:00",
  "singleUse": true
}
```

```json
{ "object": "form_prefill_link", "url": "https://sign.example.com/f/antrag-anwohnerparkausweis-k7m2p9qa?p=fp1.…", "expiresAt": "2026-10-31T22:00:00.000Z" }
```

- Die Werte stehen **verschlüsselt und signiert** im Parameter `p`; ohne den Schlüssel der Plattform lassen sie sich weder lesen noch ändern.
- `lockedKeys` sind auf der Seite schreibgeschützt und werden auch auf dem Server nicht geändert. Eingaben vom Typ `HIDDEN` lassen sich nur so (oder über `POST /v1/forms/{id}/submissions`) setzen.
- Werte sind Text: `CHECKBOX` mit `true`/`false`, Auswahlfelder mit dem Schlüssel der Option, `MULTI_SELECT` mit Schlüsseln durch Kommas getrennt. Einwilligungen und Dateien lassen sich nicht vorbelegen (422 / 9012 `prefill_key_invalid` mit `details.keys`).
- Höchstens 20 Felder und 1 KB; gültig höchstens `access.prefill.maxAgeDays` Tage (Standard 30). `singleUse: true` lässt nur den ersten Start die Werte übernehmen.
- Ein veränderter, abgelaufener, schon benutzter oder für ein anderes Formular erzeugter Link öffnet das **leere** Formular.
- **Unsignierte Parameter (`?vorname=Anna`) befüllen nichts.**
- Für einen eingebetteten Link geben Sie `embedOrigin` an (eine Ihrer erlaubten Websites); der Link enthält dann `embed=1`. Eine nicht erlaubte Adresse ergibt 403 / 9038.

## 5. E-Mail-Bestätigung

Mit `access.requireEmailVerification: true` bestätigt die Person vor dem Ausfüllen ihre E-Mail-Adresse mit einem 6-stelligen Code (15 Minuten gültig, höchstens drei Codes, fünf Versuche). Die bestätigte Adresse wird zum schreibgeschützten Wert des E-Mail-Felds (`role: SUBMITTER_EMAIL`) und steht im Eingang als `submitter.emailVerified: true`.
