> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Preference Center: die öffentliche Opt-in/Opt-out-Seite

> Konfigurieren Sie das gehostete, token-signierte Preference Center — Branding, Kanäle, Frequenzoptionen und die GDPR-Lösch-Kachel — erstellen Sie einen Pro-Kontakt-Link und wissen Sie genau, welche Compliance-Oberflächen ein Opt-out beschreibt (Einwilligung, Suppression, STOP-Zaun, Audit).

# Preference Center: die öffentliche Opt-in/Opt-out-Seite

Das **Preference Center** ist eine öffentliche Seite, auf der ein Kontakt seine eigenen Kanal-Opt-ins, Abonnement-Themen, Nachrichtenfrequenz und (wenn aktiviert) einen Datenlöschungsantrag verwaltet — kein Konto, kein Login. Jeder Kontakt erreicht sie über einen **signierten Link**: Die URL trägt ein HMAC-SHA256-Token (`v1.<payload>.<signature>`), das nach 30 Tagen abläuft, sodass die Seite Self-Service bleibt, aber auf genau einen Kontakt in genau einer Organisation beschränkt ist.

Die zusammengefasste Endpunkt-Oberfläche lebt auch in [Send Gates](/compliance/send-gates#preference-center); dieser Leitfaden ist die vollständige Schritt-für-Schritt-Anleitung: jedes Konfigurationsfeld, wo der Link platziert wird, was die öffentliche Seite zurückgibt und welche Compliance-Oberflächen ein Opt-out oder Opt-in beschreibt.

Alle Endpunkte unten laufen unter `https://api.orbit.devotel.io/api/v1/compliance`.

> Englisches Original: [Preference center: the public opt-in/opt-out page](/guides/preference-center-opt-out-page).

<Note>
  Das Preference Center ist ein **mandantenseitiges Steuerungselement**: Sie wählen die Kanäle, Themen und das Branding, und Ihre Organisation hält die Einwilligungsnachweise. Orbit betreibt die Plattform; die Einwilligungsentscheidung gehört dem Kontakt. Dieser Leitfaden ist keine Rechtsberatung — bestätigen Sie Ihre Pflichten mit Ihrer Rechtsabteilung.
</Note>

***

## 1. Einmal konfigurieren: POST/GET /preference-center

Setzen Sie die Konfiguration mit `POST /preference-center` (Owner/Admin-API-Schlüssel). Der Endpunkt upsertet die Konfiguration in die Einstellungen Ihrer Organisation und gibt das gespeicherte Objekt zurück — erneut ausführen, um zu aktualisieren. `GET /preference-center` liest die aktuelle Konfiguration zurück; vor der Konfiguration gibt sie `enabled: false` mit einem Hinweistext zurück.

### Konfigurationsfelder

Jedes Feld wird serverseitig validiert — eine abgelehnte POST gibt `422` mit Feld-Level-Problemen (`field`, `message`) zurück, damit Sie erkennen, welches Feld fehlgeschlagen ist.

| Feld                   | Typ             | Standard                                             | Was es steuert                                                                                           |
| ---------------------- | --------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `enabled`              | boolean         | `true`                                               | Hauptschalter. Wenn `false`, gibt die öffentliche Seite „nicht verfügbar" zurück.                        |
| `companyName`          | string (1–200)  | **erforderlich**                                     | Der auf der gehosteten Seite angezeigte Firmenname.                                                      |
| `logoUrl`              | string (URL)    | —                                                    | Das von der Seite übernommene Logo. URLs sind auf `http://` oder `https://` beschränkt.                  |
| `primaryColor`         | hex `#rrggbb`   | `#2563eb`                                            | Akzentfarbe für die Seiten-Benutzeroberfläche.                                                           |
| `headerText`           | string (≤500)   | `"Communication Preferences"`                        | Seitentitel.                                                                                             |
| `footerText`           | string (≤1000)  | `"Wir respektieren Ihre Kommunikationspräferenzen."` | Fußzeilen-Text der Seite.                                                                                |
| `optOutMessage`        | string (≤500)   | `"Verwalten Sie Ihre Präferenzen"`                   | Fußzeilen-Label, das verwendet wird, wenn der Link automatisch an ausgehende Nachrichten angehängt wird. |
| `channels`             | enum array (≥1) | `["sms","email"]`                                    | Auf der Seite angebotene Kanäle. Erlaubte Werte: `sms`, `whatsapp`, `email`, `rcs`, `viber`, `voice`.    |
| `showFrequencyOptions` | boolean         | `true`                                               | Frequenzselektor zeigen (`all`, `important_only`, `weekly_digest`, `monthly_digest`).                    |
| `showGdprDelete`       | boolean         | `true`                                               | Die Datenlöschungs-Kachel zeigen (siehe Abschnitt 6).                                                    |
| `customCss`            | string (≤10000) | —                                                    | Zusätzliches CSS, das in die gehostete Seite injiziert wird.                                             |
| `redirectUrl`          | string (URL)    | —                                                    | Wohin der Kontakt nach Abschluss eines Opt-outs geschickt wird. Nur `http(s)`-Schemata.                  |
| `topics`               | array (≤50)     | `[]`                                                 | Abonnementgruppen, die ein Kontakt unabhängig vom Kanal-Schalter ein-/ausschaltet (siehe unten).         |

### Abonnement-Themen

Ein Thema ist eine benannte Gruppe — Newsletter, Produkt-Updates, Rechnungswarnungen — die ein Kontakt **ohne** den ganzen Kanal zu berühren ein- oder ausschaltet. Thema-`id`s müssen einzigartig sein und dem Slug-Muster (`[a-z0-9][a-z0-9_-]{0,63}`) entsprechen; jeder Eintrag hat:

* `name` (1–120 Zeichen) — der auf der Seite angezeigte Name.
* `description` (optional, ≤500) — eine Zeile Kontext neben der Kachel.
* `defaultOptIn` (Standard `false`) — wie ein Kontakt ohne aufgezeichnete Präferenz behandelt wird.
* `archived` (optional) — archivierte Themen bleiben im Audit-Trail, erscheinen aber nicht mehr auf der Seite.

Lint-Ebenen-Fallstricke: URLs, die die `http(s)`-Schema-Überprüfung nicht bestehen, werden vorab abgelehnt, und doppelte Thema-`id`s schlagen mit „Thema-IDs müssen einzigartig sein" fehl, statt still überschrieben zu werden.

***

## 2. Pro-Kontakt-Link erstellen

Nach der Konfiguration generieren Sie mit `POST /preference-center/link` einen Link für jeweils einen Kontakt:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/compliance/preference-center/link" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contactId": "cnt_01H…" }'
```

Die Antwort gibt `link` zurück — eine URL der Form `${DEVOTEL_WEB_URL}/preferences?token=v1…`. Beachtenswerte Punkte:

* **Der Link zielt auf die gehostete Seite, nicht auf das JSON-Endpunkt.** Kopieren Sie ihn wörtlich in Ihre Fußzeilen-/Absender-Vorlagen; die Seite selbst ruft den Daten-Endpunkt unter der Haube ab.
* **30-Tage-TTL.** Danach wird das Token als abgelaufen geprüft und der Kontakt muss einen neuen Link anfordern (das Erstellen eines neuen erfordert einen API-Aufruf).
* **Die Seite ist zum Erstellungszeitpunkt sprachagnostic.** Die Web-App löst eine Umleitung unter Beibehaltung der `?token=`-Abfrage, sodass Sie das Locale des Kontakts nicht erraten müssen.

### Wo er platziert wird

* **E-Mail-Fußzeile (primär).** Hängen Sie den generierten Link (oder die kurze, verfolgte Variante, die Ihr Mailer verwendet) in den Abmeldebereich der Marketingvorlagen an.
* **SMS / WhatsApp-Fallback.** Wenn die Nachricht keinen Fußzeilenblock hat, hängen Sie den Link inline an: `{optOutMessage}: {link}`. Der Helfer, der ausgehende Körper erstellt, akzeptiert einen vorab erstellten Kurzlink, sodass Ihr Abmeldeklick weiterhin die normale Klick-Attribution erhält.
* **Suppression-getriebener Wieder-Opt-in.** Wenn ein Kontakt über einen anderen Ablauf wieder opt-int, können Sie ihm einen frischen Link geben, damit er dieselbe Self-Service-Seite erhält.

Ein Roh-Link funktioniert auch, wenn die Kurzlink-Erstellung fehlschlägt — der Fallback ist additiv, nie lasttragend für Compliance.

***

## 3. Die öffentliche Token-Seite

Die gehostete Seite liest und schreibt über zwei nicht authentifizierte Endpunkte, die vom signierten Token geschützt sind:

* `GET /preferences/:token` — gibt die Seiten-Ladung zurück.
* `PUT /preferences/:token` — wendet Aktualisierungen an.

Ungültige Tokenformen geben `400 INVALID_TOKEN` zurück; abgelaufene oder manipulierte Token geben `401 TOKEN_EXPIRED` mit „Bitte einen neuen Link anfordern."

### GET-Antwort

Die Ladung bündelt den aktuellen Zustand des Kontakts und die Organisations-Konfiguration:

```json theme={null}
{
  "contactId": "cnt_01H…",
  "displayName": "…",
  "email": "m*****@example.com",
  "phone": "+15551****…",
  "channelPreferences": { "sms": "opted_in", "email": "opted_out" },
  "frequencyPreference": "all",
  "channels": ["sms", "email"],
  "topics": [ { "id": "newsletter", "name": "Newsletter", "defaultOptIn": false } ],
  "topicPreferences": { "newsletter": "opted_in" },
  "consentHistory": [
    { "channel": "all", "state": "opted_out", "topicId": "newsletter", "occurredAt": "2026-09-01T…" }
  ],
  "config": { /* die Konfiguration der Organisation */ }
}
```

E-Mail und Telefon sind **maskiert** in der öffentlichen Antwort — die Seite zeigt nie den Roh-Identifikator, mit dem sie aufgerufen wird. `consentHistory` ist der Newest-First-Opt-in/Opt-out-Audit-Trail des Kontakts, auf 20 Zeilen begrenzt, gezogen aus demselben Einwilligungsledger, den Ihre Operatoren im Dashboard sehen.

### PUT-Anfragetext

```json theme={null}
{
  "channelPreferences": {
    "sms": "opted_in",
    "email": "opted_out"
  },
  "frequencyPreference": "important_only",
  "topicPreferences": { "newsletter": "opted_in" },
  "requestDataDeletion": false
}
```

* `channelPreferences` — Teilabbildung erlaubt (Zod-Partial-Record); **mindestens ein Kanal erforderlich**.
* `frequencyPreference` — optional, einer von `all`, `important_only`, `weekly_digest`, `monthly_digest`.
* `topicPreferences` — optional `{ topicId: opted_in | opted_out }` Abbildung, validiert gegen Ihre konfigurierten Themen; unbekannte IDs werden ignoriert.
* `requestDataDeletion` — setzt neben dem Opt-out einen GDPR-Löschungsantrag (siehe Abschnitt 6).

Eine 422-Antwort trägt Feld-Level-Probleme, damit das gehostete Formular auf die ungültige Wahl zeigen kann.

***

## 4. Wie Aktualisierungen fließen

Ein hier geschriebenes Opt-in/Opt-out ist **nicht nur ein UI-Flag** — dieselben vier Compliance-Oberflächen, die ein STOP-Keyword beschreibt, werden aktualisiert:

* **Einwilligungsledger.** Eine `consent_records`-Zeile pro Kanal (oder pro Thema) wird mit `source: preference_center` angehängt — Ihr GDPR-Artikel-7-Beweislast-Audit-Trail.
* **Suppression-Liste.** Auf jedem abgemeldeten Kanal wird die kanonischierte Telefon/E-Mail des Kontakts mit Scope `all` eingefügt — eine kanalübergreifende Sperre, die jedes Send-Gate liest.
* **STOP-Zaun.** Bei Opt-out wird ein Redis-Schnellpfad-Zaun gesetzt (und bei vollständigem Wieder-Opt-in gelöscht), sodass in-flight-Kampagnen-Batches die Änderung vor der langsameren Datenbank-Suppression-Propagation sehen.
* **Audit-Log.** `compliance.preference_center_updated` wird aufgezeichnet, wenn Sie die Konfiguration ändern, und Kontakt-Level-Opt-in/out-Ereignisse werden im Einwilligungsledger erfasst.

Wieder-Opt-in symmetrisch: ein vollständiger Opt-in (alle Kanäle `opted_in`) widerruft aktive Suppression-Zeilen für die Telefonnummer des Kontakts und löscht den STOP-Zaun, während das Einwilligungsledger den umgekehrten Eintrag erhält.

<Note>
  **Thema vs. Kanal.** Ein Opt-out auf Kanalebene gewinnt immer — eine Themen-Kachel verengt die Einwilligung *innerhalb* der Kanäle, die der Kontakt noch akzeptiert. Unbekannte Thema-IDs in einem PUT werden ignoriert statt persistent zu werden, sodass ein veraltetes Formular keine beliebigen Attributschlüssel schreiben kann.
</Note>

***

## 5. GDPR-Lösch-Kachel-Semantik

Wenn `showGdprDelete` aktiviert ist und der Kontakt im PUT `requestDataDeletion: true` markiert, zeichnet die API einen **Legacy-GDPR-Löschungsantrag** auf — eine `pending`-Zeile, die für Ihren Datenlöschungsprozess markiert ist — neben dem Opt-out. Diese Markierung ist beabsichtigt: die Preference-Center-Lösch-Kachel markiert den Kontakt, sie startet **nicht** die verfolgte DSAR-Pipeline.

<Warning>
  Die Preference-Center-Lösch-Kachel hat **keine SLA-Uhr, keinen entschlüsselten Datenexport und keine Artikel-17-Löschbescheinigung.** Für einen Recht-auf-Löschung-Antrag, den Ihr DPO verfolgen kann, leiten Sie ihn über das DSAR-Endpunkt ein (`POST /compliance/dsar`, Owner/Admin) — siehe [Daten-Zugriffsanfragen (DSAR)](/compliance/dsar) und den [DSAR + Verletzungsregister-Leitfaden](/guides/compliance-dsar-breach-register).
</Warning>

***

## 6. Testen

Zwei ausgearbeitete curl-Beispiele, die Sie in ein Smoke-Skript einfügen können:

**Konfiguration speichern:**

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/compliance/preference-center" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "companyName": "Acme Logistics",
    "primaryColor": "#1d4ed8",
    "channels": ["sms", "email"],
    "headerText": "Manage how Acme contacts you",
    "topics": [
      { "id": "shipping-updates", "name": "Shipping updates", "defaultOptIn": true },
      { "id": "promotions", "name": "Promotions", "defaultOptIn": false }
    ]
  }'
```

Erwartet: `201` mit der gespeicherten Konfiguration widergespiegelt.

**Link erstellen und öffentliche Endpunkte ausüben:**

```bash theme={null}
LINK=$(curl -s -X POST "https://api.orbit.devotel.io/api/v1/compliance/preference-center/link" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contactId":"cnt_01H…"}' | jq -r '.link')

TOKEN="${LINK#*token=}"

curl -s "https://api.orbit.devotel.io/api/v1/compliance/preferences/$TOKEN" | jq

curl -s -X PUT "https://api.orbit.devotel.io/api/v1/compliance/preferences/$TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channelPreferences":{"sms":"opted_out"}}' | jq
```

Erwartet: GET gibt die aktuellen Präferenzen des Kontakts zurück; PUT gibt `updated: true` plus die angewendeten Präferenzen zurück und, wenn angefordert, einen `gdprRequest`-Eintrag.

Gängige Fehler zum Prüfen: `400 INVALID_TOKEN` (malformiertes Token), `401 TOKEN_EXPIRED` (TTL abgelaufen oder Signaturkonflikt — neuen Link erstellen), `422 VALIDATION_ERROR` (Feld-Level-Probleme in der Konfiguration oder dem Aktualisierungstext) und `404 NOT_FOUND`, wenn das Preference Center deaktiviert ist oder die Kontakt-ID nicht existiert.

***

## Verwandt

* [Send Gates und Pre-Send-Schutz](/compliance/send-gates) — wo die Preference-Center-Zusammenfassung neben Ruhezeiten, Notfall-Stopp und Drosseln lebt.
* [Opt-Out & Suppression-Listen](/compliance/opt-out-suppression) — wie Scope `all` und Bulk-CSV-Importe mit dieser Oberfläche zusammenhängen.
* [Einwilligungsverwaltung](/compliance/consent-management) — die Operator-seitige API, die dasselbe Einwilligungsledger speichert.
* [DSAR-Referenz](/compliance/dsar) — die verfolgte Löschpipeline, zu der `requestDataDeletion`-Anträge geroutet werden.
