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

# مركز التفضيلات: صفحة التفضيل الإلكترونية العامة

> كوّن مركز التفضيلات المُستضاف والمُوقَّع بالرمز — العلامة التجارية، القنوات، خيارات التردد، ومفتاح حذف GDPR — أنشئ رابطًا لكل جهة اتصال وعرف بالضبط أي أسطح الامتثال يكتب إلغاء الاشتراك (الموافقة، الحظر، سور STOP، التدقيق).

# مركز التفضيلات: صفحة التفضيل الإلكترونية العامة

**مركز التفضيلات** هو صفحة عامة تدير فيها جهة الاتصال خيارات قنواتها الخاصة، ومواضيع الاشتراك، وتردد الرسائل، و (إذا فُعّل) تقديم طلب حذف البيانات — بدون حساب، بدون تسجيل دخول. تصل كل جهة اتصال إليها عبر **رابط مُوقَّع**: يحمل العنوان رمز HMAC-SHA256 (`v1.<payload>.<signature>`) ينتهي صلاحيته بعد 30 يومًا، لذا تبقى الصفحة خدمة ذاتية لكنها محصورة بجهة اتصال واحدة في منظمة واحدة.

سطح نقطة النهايات المُلخَّص يعيش أيضًا داخل [حُواجز الإرسال](/compliance/send-gates#preference-center)؛ هذا الدليل هو الجولة الكاملة: كل حقل التكوين، أين يوضع الرابط، ما تُعيد API الصفحة العامة، وأي أسطح امتثال يكتب إلغاء الاشتراك أو إعادة الاشتراك.

جميع نقاط النهايات أدناه مُعرَّفة في `https://api.orbit.devotel.io/api/v1/compliance`.

> النسخة الإنجليزية الأصلية: [Preference center: the public opt-in/opt-out page](/guides/preference-center-opt-out-page).

<Note>
  مركز التفضيلات هو **تحكم يخص المستأجر**: أنت تختار القنوات والمواضيع والعلامة التجارية، ومنظمتك تحتفظ بأدلة الموافقة. Orbit يشغّل المنصة؛ قرار الموافقة يخص جهة الاتصال. هذا الدليل ليس نصيحة قانونية — أكّد التزاماتك مع مستشارك القانوني.
</Note>

***

## 1. كوّن مرة واحدة: POST/GET /preference-center

عيّن التكوين بـ `POST /preference-center` (مفتاح API المالك/المشرف). نقطة النهاية ترفع التكوين إلى إعدادات منظمتك وتعيد الكائن المُحفوظ — أعد تشغيله لتحديث. `GET /preference-center` يقرأ التكوين الحالي؛ قبل التكوين يعيد `enabled: false` برسالة تلميح.

### حقول التكوين

كل حقل مُتحقَّق منه على الخادم — POST مرفوض يعيد `422` بمشاكل على مستوى الحقل (`field`, `message`) حتى تعرف أي حقل فشل.

| الحقل                  | النوع           | الافتراضي                      | ما يتحكم فيه                                                                                           |
| ---------------------- | --------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `enabled`              | boolean         | `true`                         | المفتاح الرئيسي. عند `false`، تعيد الصفحة العامة «غير متاح» للجهات.                                    |
| `companyName`          | string (1–200)  | **مطلوب**                      | اسم الشركة المُعروض على الصفحة المُستضافة.                                                             |
| `logoUrl`              | string (URL)    | —                              | الشعار الذي تلتقطه الصفحة. تقتصر العناوين على `http://` أو `https://`.                                 |
| `primaryColor`         | hex `#rrggbb`   | `#2563eb`                      | لون التمييز لواجهة الصفحة.                                                                             |
| `headerText`           | string (≤500)   | `"Communication Preferences"`  | عنوان الصفحة.                                                                                          |
| `footerText`           | string (≤1000)  | `"نحترم تفضيلاتك في التواصل."` | نص تذييل الصفحة.                                                                                       |
| `optOutMessage`        | string (≤500)   | `"إدارة تفضيلاتك"`             | تسمية التذييل المُستخدمة عند إرفاق الرابط بالرسائل الصادرة تلقائيًا.                                   |
| `channels`             | enum array (≥1) | `["sms","email"]`              | القنوات المُقدَّمة على الصفحة. القيم المسموح بها: `sms`, `whatsapp`, `email`, `rcs`, `viber`, `voice`. |
| `showFrequencyOptions` | boolean         | `true`                         | إظهار مُحدد التردد (`all`, `important_only`, `weekly_digest`, `monthly_digest`).                       |
| `showGdprDelete`       | boolean         | `true`                         | إظهار مفتاح حذف البيانات (انظر القسم 6).                                                               |
| `customCss`            | string (≤10000) | —                              | CSS إضافي مُحقَّق في الصفحة المُستضافة.                                                                |
| `redirectUrl`          | string (URL)    | —                              | أين تُرسَل جهة الاتصال بعد إتمام إلغاء الاشتراك. فقط مخططات `http(s)`.                                 |
| `topics`               | array (≤50)     | `[]`                           | مجموعات الاشتراك التي تُبدِّلها جهة الاتصال مستقلة عن مفتاح القناة (انظر أدناه).                       |

### مواضيع الاشتراك

الموضوع هو مجموعة مُسمَّاة — النشرة الإخبارية، تحديثات المنتج، تنبيهات الفوترة — تُبدِّلها جهة الاتصال **دون** لمس القناة كاملة. يجب أن تكون `id` الموضوعات فريدة وتُطابق نمط الـ slug (`[a-z0-9][a-z0-9_-]{0,63}`)؛ كل إدخال له:

* `name` (1–120 حرفًا) — الاسم المُعروض على الصفحة.
* `description` (اختياري، ≤500) — سطر سياق واحد معروض بجانب المفتاح.
* `defaultOptIn` (افتراضي `false`) — كيف تُعامَل جهة اتصال بدون تفضيل مُسجَّل.
* `archived` (اختياري) — المواضيع المؤرشفة تبقى في مسار التدقيق لكنها لم تعد تظهر على الصفحة.

مشاكل Lint: العناوين التي تفشل في تحقّق مخطط `http(s)` تُرفَض مقدمًا، و `id` الموضوعات المكررة تفشل مع «يجب أن تكون id الموضوعات فريدة» بدلاً من الكتابة الصامتة.

***

## 2. أنشئ رابطًا لكل جهة اتصال

بعد التكوين، أول رابط لجهة اتصال واحدة كل مرة بـ `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…" }'
```

الاستجابة تُعيد `link` — عنوان من الشكل `${DEVOTEL_WEB_URL}/preferences?token=v1…`. نقاط للانتباه:

* **الرابط يستهدف الصفحة المُستضافة، ليس نقطة النهاية JSON.** انسخه حرفيًا في قوالب التذييل/المرسل؛ الصفحة نفسها تجلب نقطة النهاية البيانات في الخلفية.
* **TTL 30 يومًا.** بعد ذلك يتم التحقق من الرمز كمنتهي الصلاحية ويجب على جهة الاتصال طلب رابط جديد (إنشاء جديد يأخذ استدعاء API واحد).
* **الصفحة لا تعرف اللغة عند الإنشاء.** تطبيق الويب يُحل إعادة توجيه مع الحفاظ على استعلام `?token=`، لذا لا تحتاج لتخمين لغة جهة الاتصال.

### أين يوضع

* **تذييل البريد الإلكتروني (الأساسي).** أرفق الرابط المُولَّد (أو المتغير القصير المُتتبَّع الذي يستخدمه مرسلك) في منطقة إلغاء الاشتراك في قوالب التسويق.
* **احتياط SMS / WhatsApp.** عندما لا يحتوي الرسالة على كتلة تذييل، أرفق الرابط في السطر: `{optOutMessage}: {link}`. المُساعِد الذي يبني أجسام الصادرة يقبل رابطًا قصيرًا مُسبَّقًا، لذا ما زالت نقرة إلغاء الاشتراك تأخذ التَنسِيب العادي للنقر.
* **إعادة اشتراك موجَّهة بالحظر.** عندما تعيد جهة اتصال الاشتراك عبر تدفق آخر، يمكنك إعطاؤها رابطًا جديدًا لتأخذ نفس صفحة الخدمة الذاتية.

الرابط الخام لا يزال يعمل إذا فشل إنشاء الرابط القصير — الاحتياط إضافي، ليس حملاً أساسيًا للامتثال أبدًا.

***

## 3. صفحة الرمز العامة

تقرأ الصفحة المُستضافة وتكتب عبر نقطتي نهاية غير مُصادَق عليهما محميتين بالرمز المُوقَّع:

* `GET /preferences/:token` — تُعيد حمولة الصفحة.
* `PUT /preferences/:token` — تُطبَّق التحديثات.

أشكال الرمز غير الصالحة تُعيد `400 INVALID_TOKEN`؛ الرموز المنتهية أو المُعبَّث بها تُعيد `401 TOKEN_EXPIRED` مع «يرجى طلب رابط جديد.»

### استجابة GET

الحمولة تجمّع حالة جهة الاتصال الحالية وتكوين المنظمة:

```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": { /* تكوين مركز التفضيلات */ }
}
```

البريد الإلكتروني والهاتف **مُخوَّمان** في الاستجابة العامة — الصفحة لا تعرض أبدًا المُعرِّف الخام الذي تُستدعى به. `consentHistory` هو مسار تدقيق opt-in/opt-out الأحدث أولاً لجهة الاتصال، مُقيَّد بـ 20 صفًا، مُستخرَج من نفس دفتر الموافقة الذي يراه المشغّلون في لوحة التحكم.

### جسم طلب PUT

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

* `channelPreferences` — خريطة جزئية مسموحة (Zod سجل جزئي)؛ **متطلب قناة واحدة على الأقل**.
* `frequencyPreference` — اختياري، واحد من `all`, `important_only`, `weekly_digest`, `monthly_digest`.
* `topicPreferences` — خريطة `{ topicId: opted_in | opted_out }` اختيارية مُتحقَّقة مقابل موضوعاتك المُكوَّنة؛ الـ id غير المعروفة تُتجاهَل.
* `requestDataDeletion` — تُعيَّن طلب حذف GDPR بجانب إلغاء الاشتراك (انظر القسم 6).

استجابة 422 تحمل مشاكل على مستوى الحقل حتى تستطيع النموذج المُستضاف الإشارة إلى الاختيار غير الصالح.

***

## 4. كيف تتدفق التحديثات

إلغاء الاشتراك/إعادة الاشتراك المكتوب هنا **ليس مجرد علم واجهة مستخدم** — نفس أربع أسطح امتثال التي تكتب كلمة STOP تُحدَّث:

* **دفتر الموافقة.** صف `consent_records` واحد لكل قناة (أو لكل موضوع) يُرفَق بـ `source: preference_center` — مسار تدقيق عبء البراهين GDPR المادة 7.
* **قائمة الحظر.** على أي قناة مُلغاة الاشتراك، يُدخَل الهاتف/البريد الإلكتروني القانوني لجهة الاتصال بمجال `all` — حظر عبر القنوات تقرأه كل بوابة إرسال.
* **سور STOP.** يُوضَع سور مسار سريع Redis عند إلغاء الاشتراك (ويُمحى عند إعادة الاشتراك الكامل)، لذا تُطْلع دفعات الحملات أثناء الطيران على التغيير قبل أن تنتشر الحظر الأبطأ من قاعدة البيانات.
* **سجل التدقيق.** `compliance.preference_center_updated` يُسجَّل عند تغيير التكوين، وأحداث opt-in/out على مستوى جهة الاتصال تُلتقَط في دفتر الموافقة.

إعادة الاشتراك متناظرة: إعادة اشتراك كاملة (كل القنوات `opted_in`) تُلغي صفوف الحظر النشطة لهاتف جهة الاتصال وتُمحي سور STOP، بينما يكسب دفتر الموافقة الإدخال المُعكوس.

<Note>
  **الموضوع مقابل القناة.** إلغاء اشتراك مستوى القناة يفوز دائمًا — مفتاح الموضوع يُضيِّق الموافقة *داخل* القنوات التي ما زالت جهة الاتصال تقبلها. id الموضوعات غير المعروفة في PUT تُتجاهَل بدلاً من أن تُثبَّت، لذا لا تستطيع نموذج قديم كتابة مفاتيح سمة عشوائية.
</Note>

***

## 5. دلاليّات مفتاح حذف GDPR

عندما يكون `showGdprDelete` مُفعَّلًا وتختار جهة الاتصال `requestDataDeletion: true` في PUT، تُسجَّل API **طلب حذف GDPR قديمًا** — صف `pending` مُعلَّم لعملية حذف البيانات — بجانب إلغاء الاشتراك. تلك العلامة متعمَّدة: مفتاح حذف مركز التفضيلات يُعلِّم جهة الاتصال، إنه **لا** يبدأ مسار DSAR المُتتبَّع.

<Warning>
  مفتاح حذف مركز التفضيلات ليس له **ساعة SLA، لا تصدير بيانات مُشفَّر، ولا شهادة حذف المادة 17.** لطلب حق الحذف يتتبَّعه مسؤول حماية البيانات لديك، وجِّهه عبر نقطة نهاية DSAR (`POST /compliance/dsar`، المالك/المشرف) — انظر [طلبات الوصول إلى بيانات الموضوع (DSAR)](/compliance/dsar) و[دليل DSAR + سجل الانتهاكات](/guides/compliance-dsar-breach-register).
</Warning>

***

## 6. اختبارها

مثالان curl مُعمَّل يمكنك لصقيهما في نص دخين:

**حفظ التكوين:**

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

المُتوقَّع: `201` مع التكوين المُحفوظ مُعكوسًا.

**إنشاء رابط وممارسة نقاط النهايات العامة:**

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

المُتوقَّع: GET تُعيد تفضيلات جهة الاتصال الحالية؛ PUT تُعيد `updated: true` بالإضافة إلى التفضيلات المُطبَّقة، وعند الطلب، إدخال `gdprRequest`.

الفشل الشائع للتحقق: `400 INVALID_TOKEN` (رمز مُشكَّل)، `401 TOKEN_EXPIRED` (TTL انقضى أو عدم تطابق التوقيع — أنشئ رابطًا جديدًا)، `422 VALIDATION_ERROR` (مشاكل على مستوى الحقل في التكوين أو جسم التحديث) و `404 NOT_FOUND` عندما يكون مركز التفضيلات مُعطَّلًا أو id جهة الاتصال غير موجود.

***

## متعلق

* [حُواجز الإرسال وحُرَّاس ما قبل الإرسال](/compliance/send-gates) — أين يعيش مُلخَّص مركز التفضيلات بجانب ساعات الصمت، الإيقاف الطارئ، والقِيود.
* [إلغاء الاشتراك وقوائم الحظر](/compliance/opt-out-suppression) — كيف يتعلق مجال `all` واستيراد CSV الضخم بهذا السطح.
* [إدارة الموافقة](/compliance/consent-management) — API جانب المُشغِّل الذي يُخزِّن نفس دفتر الموافقة.
* [مرجع DSAR](/compliance/dsar) — مسار الحذف المُتتبَّع الذي تُوجَّه إليه طلبات `requestDataDeletion`.
