> ## 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.

# Centre de préférences : page publique opt-in/opt-out

> Configurez le centre de préférences hébergé et signé par jeton — branding, canaux, options de fréquence et la bascule de suppression GDPR — créez un lien par contact et sachez exactement quelles surfaces de conformité un opt-out écrit (consentement, suppression, clôture STOP, audit).

# Centre de préférences : page publique opt-in/opt-out

Le **centre de préférences** est une page publique où un contact gère ses propres opt-ins par canal, ses sujets d'abonnement, sa fréquence de message et (si vous l'activez) dépose une demande de suppression de données — pas de compte, pas de connexion. Chaque contact y accède via un **lien signé** : l'URL porte un jeton HMAC-SHA256 (`v1.<payload>.<signature>`) qui expire après 30 jours, de sorte que la page reste en libre-service mais limitée à un seul contact dans une seule organisation.

La surface des points d'accès résumée vit aussi dans [Barrières d'envoi](/compliance/send-gates#preference-center) ; ce guide est le parcours complet : chaque champ de configuration, où placer le lien, ce que renvoie l'API de la page publique et quelles surfaces de conformité un opt-out ou opt-in écrit.

Tous les points d'accès ci-dessous sont racinés à `https://api.orbit.devotel.io/api/v1/compliance`.

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

<Note>
  Le centre de préférences est un **contrôle appartenant au locataire** : vous choisissez les canaux, les sujets et le branding, et votre organisation détient la preuve de consentement. Orbit exploite la plateforme ; la décision de consentement appartient au contact. Ce guide n'est pas un avis juridique — confirmez vos obligations avec votre conseiller juridique.
</Note>

***

## 1. Configurer une fois : POST/GET /preference-center

Définissez la configuration avec `POST /preference-center` (clé API propriétaire/administrateur). Le point d'accès fait un upsert de la configuration dans les paramètres de votre organisation et renvoie l'objet enregistré — relancez-le pour mettre à jour. `GET /preference-center` lit la configuration actuelle ; avant la configuration, elle renvoie `enabled: false` avec un message d'indication.

### Champs de configuration

Chaque champ est validé côté serveur — un POST rejeté renvoie `422` avec des problèmes au niveau du champ (`field`, `message`) pour identifier celui qui a échoué.

| Champ                  | Type            | Défaut                                                | Ce qu'il contrôle                                                                                                |
| ---------------------- | --------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `enabled`              | boolean         | `true`                                                | Interrupteur principal. Quand `false`, la page publique renvoie « non disponible » aux contacts.                 |
| `companyName`          | string (1–200)  | **requis**                                            | Nom de l'entreprise affiché sur la page hébergée.                                                                |
| `logoUrl`              | string (URL)    | —                                                     | Logo récupéré par la page. Les URL sont limitées à `http://` ou `https://`.                                      |
| `primaryColor`         | hex `#rrggbb`   | `#2563eb`                                             | Couleur d'accent de l'interface de la page.                                                                      |
| `headerText`           | string (≤500)   | `"Communication Preferences"`                         | Titre de la page.                                                                                                |
| `footerText`           | string (≤1000)  | `"Nous respectons vos préférences de communication."` | Texte du pied de page.                                                                                           |
| `optOutMessage`        | string (≤500)   | `"Gérez vos préférences"`                             | Libellé du pied de page utilisé lorsque le lien est automatiquement ajouté aux messages sortants.                |
| `channels`             | enum array (≥1) | `["sms","email"]`                                     | Canaux proposés sur la page. Valeurs autorisées : `sms`, `whatsapp`, `email`, `rcs`, `viber`, `voice`.           |
| `showFrequencyOptions` | boolean         | `true`                                                | Afficher le sélecteur de fréquence (`all`, `important_only`, `weekly_digest`, `monthly_digest`).                 |
| `showGdprDelete`       | boolean         | `true`                                                | Afficher la bascule de suppression de données (voir section 6).                                                  |
| `customCss`            | string (≤10000) | —                                                     | CSS supplémentaire injecté dans la page hébergée.                                                                |
| `redirectUrl`          | string (URL)    | —                                                     | Où le contact est envoyé après avoir complété un opt-out. Schémas `http(s)` uniquement.                          |
| `topics`               | array (≤50)     | `[]`                                                  | Groupes d'abonnement qu'un contact active ou désactive indépendamment du commutateur de canal (voir ci-dessous). |

### Sujets d'abonnement

Un sujet est un groupe nommé — Bulletin d'information, Mises à jour de produit, Alertes de facturation — qu'un contact active ou désactive **sans** toucher à tout le canal. Les `id` de sujets doivent être uniques et correspondre au modèle slug (`[a-z0-9][a-z0-9_-]{0,63}`) ; chaque entrée a :

* `name` (1–120 caractères) — le nom affiché sur la page.
* `description` (optionnel, ≤500) — une ligne de contexte affichée à côté de la bascule.
* `defaultOptIn` (défaut `false`) — comment un contact sans préférence enregistrée est traité.
* `archived` (optionnel) — les sujets archivés restent dans la piste d'audit mais n'apparaissent plus sur la page.

Pièges de niveau lint : les URL qui échouent à la validation du schéma `http(s)` sont rejetées d'emblée, et les `id` de sujets dupliqués échouent avec « Les id de sujets doivent être uniques » plutôt qu'en écrasant silencieusement.

***

## 2. Créer un lien par contact

Une fois configuré, générez un lien pour un contact à la fois avec `POST /preference-center/link` :

```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…" }'
```

La réponse renvoie `link` — une URL de la forme `${DEVOTEL_WEB_URL}/preferences?token=v1…`. Points à noter :

* **Le lien cible la page hébergée, pas le point d'accès JSON.** Copiez-le tel quel dans vos gabarits de pied de page/expéditeur ; la page elle-même récupère le point d'accès aux données en arrière-plan.
* **TTL de 30 jours.** Ensuite, le jeton se vérifie comme expiré et le contact doit demander un nouveau lien (en créer un nouveau prend un appel API).
* **La page est indépendante de la locale au moment de la création.** L'application web résout une redirection tout en conservant la requête `?token=`, vous n'avez donc pas à deviner la locale du contact.

### Où le placer

* **Pied de page e-mail (principal).** Ajoutez le lien généré (ou la variante courte tracée utilisée par votre expéditeur) dans la zone de désinscription des gabarits marketing.
* **Secours SMS / WhatsApp.** Lorsque le message n'a pas de bloc de pied de page, ajoutez le lien en ligne : `{optOutMessage}: {link}`. L'assistant qui construit les corps sortants accepte un lien court pré-créé, de sorte que votre clic de désinscription reçoit toujours l'attribution normale des clics.
* **Retour opt-in piloté par suppression.** Lorsqu'un contact se ré-opt-in via un autre flux, vous pouvez lui donner un lien frais pour qu'il obtienne la même page en libre-service.

Un lien brut fonctionne encore si la création de lien court échoue — le secours est additif, jamais porteur de charge pour la conformité.

***

## 3. La page publique à jeton

La page hébergée lit et écrit via deux points d'accès non authentifiés protégés par le jeton signé :

* `GET /preferences/:token` — renvoie la charge utile de la page.
* `PUT /preferences/:token` — applique les mises à jour.

Les formes de jeton invalides renvoient `400 INVALID_TOKEN` ; les jetons expirés ou falsifiés renvoient `401 TOKEN_EXPIRED` avec « Veuillez demander un nouveau lien. »

### Réponse GET

La charge utile regroupe l'état actuel du contact et la configuration de l'organisation :

```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": { /* la configuration du centre de préférences de l'organisation */ }
}
```

L'e-mail et le téléphone sont **masqués** dans la réponse publique — la page n'affiche jamais l'identifiant brut avec lequel elle est appelée. `consentHistory` est la piste d'audit opt-in/opt-out la plus récente du contact, plafonnée à 20 lignes, tirée du même registre de consentement que vos opérateurs voient dans le tableau de bord.

### Corps de requête PUT

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

* `channelPreferences` — carte partielle autorisée (Zod enregistrement partiel) ; **au moins un canal requis**.
* `frequencyPreference` — optionnel, l'un de `all`, `important_only`, `weekly_digest`, `monthly_digest`.
* `topicPreferences` — carte `{ topicId: opted_in | opted_out }` optionnelle validée par rapport à vos sujets configurés ; les id inconnus sont ignorés.
* `requestDataDeletion` — définit une demande de suppression GDPR à côté de l'opt-out (voir section 6).

Une réponse 422 porte des problèmes au niveau du champ afin que le formulaire hébergé puisse pointer vers le choix invalide.

***

## 4. Comment les mises à jour circulent

Un opt-in/opt-out écrit ici n'est **pas seulement un drapeau UI** — les mêmes quatre surfaces de conformité qu'un mot-clé STOP écrit sont mises à jour :

* **Registre de consentement.** Une ligne `consent_records` par canal (ou par sujet) est ajoutée avec `source: preference_center` — votre piste d'audit de charge de la preuve GDPR Article 7.
* **Liste de suppression.** Sur tout canal opt-out, le téléphone/e-mail canonique du contact est inséré avec le scope `all` — un bloc intercanal que chaque porte d'envoi lit.
* **Clôture STOP.** Une clôture de chemin rapide Redis est mise en place à l'opt-out (et supprimée lors d'un retour opt-in complet), de sorte que les lots de campagnes en vol voient le changement avant que la suppression de base de données plus lente ne se propage.
* **Journal d'audit.** `compliance.preference_center_updated` est enregistré lorsque vous modifiez la configuration, et les événements opt-in/out au niveau du contact sont capturés dans le registre de consentement.

Retour opt-in symétrique : un opt-in complet (tous les canaux `opted_in`) révoque les lignes de suppression actives pour le téléphone du contact et supprime la clôture STOP, tandis que le registre de consentement gagne l'entrée inversée.

<Note>
  **Sujet vs. canal.** Un opt-out au niveau du canal l'emporte toujours — une bascule de sujet réduit le consentement *dans* les canaux que le contact accepte toujours. Les id de sujets inconnus dans un PUT sont ignorés plutôt que persistés, de sorte qu'un formulaire obsolète ne peut pas écrire des clés d'attribut arbitraires.
</Note>

***

## 5. Sémantique de la bascule de suppression GDPR

Lorsque `showGdprDelete` est activé et que le contact coche `requestDataDeletion: true` dans le PUT, l'API enregistre une **demande de suppression GDPR héritée** — une ligne `pending` marquée pour votre processus de suppression de données — à côté de l'opt-out. Cette marque est intentionnelle : la bascule de suppression du centre de préférences marque le contact, elle ne lance **pas** la pipeline DSAR suivie.

<Warning>
  La bascule de suppression du centre de préférences n'a **pas d'horloge SLA, pas d'export de données décryptées et pas de certificat d'effacement de l'article 17.** Pour une demande de droit à l'effacement que votre DPO peut suivre, routez-la via le point d'accès DSAR (`POST /compliance/dsar`, propriétaire/administrateur) — voir [Demandes d'accès aux données (DSAR)](/compliance/dsar) et le [Guide DSAR + registre des violations](/guides/compliance-dsar-breach-register).
</Warning>

***

## 6. La tester

Deux exemples curl élaborés que vous pouvez coller dans un script de fumée :

**Enregistrer la configuration :**

```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 }
    ]
  }'
```

Attendu : `201` avec la configuration enregistrée reflétée.

**Créer un lien et exercer les points d'accès publics :**

```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
```

Attendu : GET renvoie les préférences actuelles du contact ; PUT renvoie `updated: true` plus les préférences appliquées et, lorsque demandé, une entrée `gdprRequest`.

Échecs courants à vérifier : `400 INVALID_TOKEN` (jeton mal formé), `401 TOKEN_EXPIRED` (TTL dépassé ou incohérence de signature — créez un nouveau lien), `422 VALIDATION_ERROR` (problèmes au niveau du champ dans la configuration ou le corps de mise à jour) et `404 NOT_FOUND` lorsque le centre de préférences est désactivé ou que l'id du contact n'existe pas.

***

## Liens connexes

* [Barrières d'envoi et gardes pré-envoi](/compliance/send-gates) — où vit le résumé du centre de préférences aux côtés des heures de silence, de l'arrêt d'urgence et des limitations.
* [Opt-Out & Listes de suppression](/compliance/opt-out-suppression) — comment le scope `all` et les importations CSV en vrac se rapportent à cette surface.
* [Gestion du consentement](/compliance/consent-management) — l'API côté opérateur qui stocke le même registre de consentement.
* [Référence DSAR](/compliance/dsar) — la pipeline d'effacement suivie dans laquelle router les demandes `requestDataDeletion`.
