واجهة API والويب هوك
اربط الكاشير أو متجرك أو نظامك الخاص بـ OPT Retention: تسجيل العملاء والعمليات تلقائيًا، وإشعار فوري عند كل حدث. مفاتيح لكل تكامل، سجل طلبات، وإعادة إرسال عند الفشل.
البدء في 5 دقائق
واجهة API تتيح لنظام الكاشير أو المتجر أو أي برنامج لديك أن يسجّل العملاء والعمليات تلقائيًا في حسابك، وأن يستقبل إشعارًا فوريًا (ويب هوك) عند كل حدث مهم: تسجيل عميل، عملية، مكافأة، تقييم، موعد — وأن يزامن كاتالوج المنتجات والخدمات بصوره كروابط بدل إعادة إدخاله (التفاصيل).
- من لوحة التحكم: الإدارة ← API و Webhooks. الميزة ضمن باقة الاحتراف أو كإضافة «ربط API» على الباقات الأخرى.
- اضغط + مفتاح API، سمِّه (مثلًا «كاشير الفرع الرئيسي») وحدد النطاقات. السر يظهر مرة واحدة فقط — انسخه فورًا.
- جرّب أول طلب:
curl https://retention.aimenu.me/api/v1/customers?limit=5 \
-H "Authorization: Bearer grs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
https://retention.aimenu.me/api/v1 · كل الطلبات والردود JSON بترميز UTF-8 · التواريخ بصيغة ISO 8601 (2026-09-22T14:05:00Z) · المبالغ بالهللات/القروش كأعداد صحيحة (amount_minor) أو كمبلغ عشري (amount).المصادقة والنطاقات
أرسل المفتاح في ترويسة Authorization: Bearer <المفتاح> (أو X-Api-Key). المفتاح يخص منشأة واحدة ولا يرى غيرها.
| النطاق | يسمح بـ |
|---|---|
customers:read | عرض العملاء والبحث فيهم |
customers:write | إنشاء عميل |
transactions:write | تسجيل عملية شراء (تُحتسب نقاطها فورًا) |
catalog:read | قراءة المنتجات والخدمات المحفوظة (مزامنة الكاتالوج) |
catalog:write | دفع/تحديث/أرشفة المنتجات والخدمات من نظام خارجي |
customers:* / * | كل نطاقات المجموعة / كل شيء |
المفتاح يتوقف تلقائيًا إذا انتهى الاشتراك أو انتقلت المنشأة إلى باقة بلا API (ردّ 403).
العملاء
GET /api/v1/customers
قائمة العملاء مع ترقيم بالمؤشر (cursor).
| معامل | النوع | الوصف |
|---|---|---|
limit | عدد 1–100 | حجم الصفحة (افتراضي 50) |
cursor | نص | قيمة meta.next_cursor من الصفحة السابقة |
search | نص | بحث في الاسم أو الجوال |
lifecycle_state | نص | new · active · at_risk · churned |
{
"ok": true,
"data": [
{ "id": "cust_8f2…", "name": "سارة العتيبي", "phone": "+9665xxxxxxxx",
"lifecycle_state": "active", "visit_count": 12, "points": 340,
"last_visit_at": "2026-09-18T16:20:00Z", "return_due_at": "2026-10-02T16:20:00Z" }
],
"meta": { "limit": 50, "count": 1, "next_cursor": null, "has_more": false }
}
GET /api/v1/customers/{id}
عميل واحد بالشكل نفسه. غير موجود ← 404.
POST /api/v1/customers
| حقل | مطلوب | القواعد |
|---|---|---|
name | نعم | حتى 120 حرفًا |
phone | نعم | جوال؛ يُطبَّع تلقائيًا بمفتاح دولة المنشأة (05xxxxxxxx يصبح +9665xxxxxxxx). الجوال المكرر ← 422 |
expected_return_days | لا | 1–3650 (افتراضي 30) — دورة العودة المتوقعة لهذا العميل |
branch_id | لا | معرّف فرع من فروعك |
preferred_locale | لا | ar-SA افتراضيًا |
curl -X POST https://retention.aimenu.me/api/v1/customers \
-H "Authorization: Bearer grs_…" -H "Content-Type: application/json" \
-d '{"name":"سارة العتيبي","phone":"0551234567"}'
# 201 → { "ok": true, "data": { "id": "cust_…", … } }
الاستيراد بالجملة
لنقل عملاء أو فواتير سابقة من نظام قديم دفعة واحدة لا تحتاج API: صفحة استيراد البيانات في لوحة المنشأة تقبل ملف CSV أو لصقًا من Excel حتى 5000 صف، وتعرض معاينة قبل التنفيذ، وتسجّل الفواتير بتاريخها الحقيقي دون نقاط أو رسائل، ولكل استيراد زر تراجع. استعمل POST /customers وPOST /transactions أدناه للتكامل المستمر (عميل أو عملية لحظة حدوثها)، والاستيراد للدفعة الأولى.
تسجيل عملية شراء
POST /api/v1/transactions
يسجّل عملية للعميل ويحتسب نقاطه ومستواه ومكافآت الإحالة فورًا، ويشغّل الرسائل التلقائية (شكر بعد الزيارة، طلب التقييم).
Idempotency-Key إلزامية (8–128 حرفًا، مثل رقم الفاتورة أو UUID). إعادة إرسال الطلب نفسه بالمفتاح نفسه لا تسجّل العملية مرتين، بل تعيد العملية الأصلية مع "duplicate": true. المفتاح نفسه ببيانات مختلفة ← 409.| حقل | مطلوب | القواعد |
|---|---|---|
customer_id | نعم | معرّف عميل من منشأتك |
amount أو amount_minor | نعم | 47.50 أو 4750 (هللات) — أكبر من صفر |
currency | لا | ثلاثة أحرف؛ افتراضيًا عملة المنشأة (SAR) |
occurred_at | لا | ISO 8601؛ افتراضيًا الآن |
external_transaction_id | لا | رقم الفاتورة في نظامك — يُحفظ ويمنع التكرار أيضًا |
branch_id | لا | افتراضيًا فرع العميل المفضل |
curl -X POST https://retention.aimenu.me/api/v1/transactions \
-H "Authorization: Bearer grs_…" -H "Content-Type: application/json" \
-H "Idempotency-Key: INV-2026-000123" \
-d '{"customer_id":"cust_8f2…","amount":180.00,"external_transaction_id":"INV-2026-000123"}'
# 200 → { "ok": true, "data": { "transaction": { "id": "…", "amount_minor": 18000, "currency": "SAR", "status": "completed" }, "duplicate": false } }
نصيحة للكاشير: أرسل العملية بعد إغلاق الفاتورة واستخدم رقم الفاتورة مفتاحًا للتكرار — فتستطيع إعادة المحاولة بأمان عند انقطاع الشبكة.
مزامنة الكاتالوج (المنتجات والخدمات)
نظامك — كاشير، قائمة رقمية، نظام حجز، ERP — يملك قائمة المنتجات والخدمات الأصلية، ولا نطلب من أحد أن يعيد إدخالها عندنا. المزامنة تجعل قائمتك هنا نسخة تابعة لقائمتك هناك: الاسم، الوصف، التصنيف، السعر، الرمز/الباركود، المدة (للخدمات)، الحالة، ورابط الصورة. الصور لا تُنسخ إلى خوادمنا أبدًا؛ نحفظ الرابط فقط ويعرضه متصفح العميل من مصدره.
① الدفع (Push) — نظامك يستدعي
PUT /api/v1/catalog/sync بكامل القائمة (أو POST /api/v1/catalog/items لعنصر واحد) كل مرة يتغير شيء أو دوريًا.② الجلب (Pull) — تنشر ملف JSON على رابط HTTPS ثابت، ويسجّله صاحب المنشأة من التكاملات ← مزامنة الكاتالوج، فنجلبه نحن تلقائيًا كل 1–168 ساعة. لا يحتاج نظامك إلى مفتاح API في هذه الحالة.
النطاقان المطلوبان: catalog:write للدفع والأرشفة، catalog:read للقراءة. الميزة تتبع ميزة API نفسها؛ عناصر المنتجات تحتاج أن تكون «المنتجات والمخزون» ضمن باقة المنشأة، والخدمات تحتاج «الحجوزات» — وإلا يُرفض العنصر وحده بالرمز feature_not_available ويستمر الباقي.
هوية العنصر: source + external_id
كل عنصر تُعرّفه بـexternal_id — معرّفه الثابت في نظامك (رقم، UUID، SKU) حتى 64 حرفًا — تحت source واحد: معرّف نظامك (مثل main-pos؛ أحرف لاتينية صغيرة وأرقام و-/_، افتراضيًا api). الزوج (source, external_id) هو ما نحدّث به العنصر لاحقًا؛ لا تغيّر external_id إذا تغيّر الاسم أو السعر. مصدران مختلفان لا يتداخلان أبدًا.
التسكين الذكي (link_by_name، مفعّل افتراضيًا): إن كان صاحب المنشأة أدخل عناصر يدويًا قبل الربط، فأول مزامنة تتبنّى العنصر اليدوي الذي يطابق الاسم (أو SKU/الباركود للمنتجات) بدل تكراره، وتوسمه بمصدرك. أرسل "link_by_name": false لتعطيله.
حقول العنصر
| حقل | النوع | مطلوب | القواعد |
|---|---|---|---|
external_id | نص | نعم | ثابت في نظامك، حتى 64 حرفًا |
kind | نص | نعم | product أو service |
name | نص | نعم | 2–80 حرفًا للمنتج، 2–120 للخدمة |
description | نص | لا | حتى 300 حرف |
category | نص | لا | اسم التصنيف حتى 40 حرفًا. للخدمات يُنشأ التصنيف تلقائيًا إن لم يوجد (بلا تكرار) |
price أو price_minor | عشري / عدد صحيح | للمنتج نعم | 45.00 أو 4500 (هللات/قروش). الخدمة بلا سعر = 0 وprice_type: free |
currency | نص | لا | إن أُرسلت فيجب أن تساوي عملة المنشأة، وإلا يُرفض العنصر |
image_url | رابط | لا | https فقط، عام على الإنترنت، بلا بيانات دخول، حتى 600 حرف. يُحفظ كرابط ولا يُنزَّل. null أو غيابه = بلا صورة (تظهر الافتراضية للمنشأة) |
status | نص | لا | active (افتراضي) أو archived — الأرشفة تخفي العنصر من الكاشير وصفحة الحجز دون حذف تاريخه |
sort | عدد | لا | ترتيب العرض |
updated_at | ISO 8601 | لا | يُحفظ للمرجع فقط |
منتج فقط: sku (أحرف لاتينية/أرقام حتى 32، فريد داخل المنشأة) · barcode (4–32) · cost/cost_minor · vat_rate (15 أو 0) · track_stock (افتراضيًا false للعناصر المزامَنة — المخزون يبقى في نظامك) | |||
خدمة فقط: duration_min (5–600، افتراضيًا 30) · price_type (fixed · from · free) · bookable_online (افتراضيًا true) | |||
ما لا تلمسه المزامنة أبدًا (حقول محلية يضبطها صاحب المنشأة): تتبع المخزون وحد النفاد وحركات المخزون، الموظفون الذين يقدمون الخدمة والفروع، مضاعف النقاط، الفاصل بعد الخدمة، والصورة المرفوعة يدويًا. إعادة إرسال عنصر لم يتغيّر لا تكتب شيئًا (نقارن بصمة الحقول المزامَنة).
PUT /api/v1/catalog/sync — دفع القائمة كاملة أو جزئيًا
curl -X PUT https://retention.aimenu.me/api/v1/catalog/sync \
-H "Authorization: Bearer grs_…" -H "Content-Type: application/json" \
-d '{
"source": "main-pos",
"mode": "replace",
"items": [
{ "external_id": "P-1001", "kind": "product", "name": "شامبو مرطّب 250 مل",
"category": "عناية بالشعر", "price": 45.00, "sku": "SH-250", "barcode": "6281000101011",
"image_url": "https://cdn.your-pos.com/img/sh-250.jpg", "status": "active", "sort": 1 },
{ "external_id": "S-77", "kind": "service", "name": "قص وتصفيف",
"category": "شعر", "price": 80, "duration_min": 45, "price_type": "fixed",
"image_url": "https://cdn.your-pos.com/img/cut.jpg" }
]
}'
# 200 → { "ok": true, "data": { "source": "main-pos", "mode": "replace", "received": 2,
# "created": 1, "updated": 1, "unchanged": 0, "archived": 3, "failed": 0, "errors": [] } }
| معامل | الوصف |
|---|---|
source | معرّف نظامك (افتراضيًا api). استخدم معرّفًا ثابتًا واحدًا لكل نظام |
mode | merge (افتراضي): يضيف ويحدّث ما أرسلته فقط. replace: إضافةً إلى ذلك يؤرشف كل عنصر من نفس source لم يرد في هذه الدفعة — استخدمه عندما ترسل القائمة كاملة. لا يمس العناصر اليدوية ولا مصادر أخرى، ولا يحذف شيئًا |
items | قائمة حتى 500 عنصر في الطلب. القوائم الأكبر تُقسَّم إلى دفعات merge ثم دفعة أخيرة… أو تُبقي كل الدفعات merge وترسل الأرشفة صراحةً بـstatus: archived |
link_by_name | التسكين الذكي أعلاه (افتراضيًا true) |
الرد دائمًا 200 ما دام الطلب سليمًا: العناصر المرفوضة تُعدّ في failed وتُفصَّل في errors (حتى 50) بمكان العنصر وسبب الرفض، ويُحفظ الباقي. الطلب كله يُرفض بـ422 فقط إذا كانت items ليست قائمة أو تجاوزت 500 أو كان mode/source غير صالح.
"errors": [
{ "index": 3, "external_id": "P-9", "field": "image_url", "code": "invalid",
"message": "رابط الصورة يجب أن يبدأ بـ https://" },
{ "index": 7, "external_id": "S-2", "field": "kind", "code": "feature_not_available",
"message": "الخدمات والحجز غير متاحة ضمن الباقة الحالية" }
]
POST /api/v1/catalog/items — عنصر واحد
نفس حقول العنصر مع source اختياري في الجسم. يعيد العنصر كما حُفظ: 201 عند الإنشاء، 200 عند التحديث أو بلا تغيير، 422 مع اسم الحقل عند الخطأ، 403 feature_not_available إن كان النوع خارج الباقة.
curl -X POST https://retention.aimenu.me/api/v1/catalog/items \
-H "Authorization: Bearer grs_…" -H "Content-Type: application/json" \
-d '{"source":"main-pos","external_id":"P-1001","kind":"product","name":"شامبو مرطّب 250 مل","price":49.5,"sku":"SH-250"}'
# 200 → { "ok": true, "data": { "result": "updated", "item": { "id": "…", "kind": "product", "external_id": "P-1001", "price_minor": 4950, … } } }
DELETE /api/v1/catalog/items/{external_id}?source=main-pos
يؤرشف العنصر (لا يحذفه): يختفي من الكاشير وصفحة الحجز ويبقى في التقارير القديمة. إعادة إرساله بـstatus: active تعيده. غير موجود لهذا المصدر ← 404.
GET /api/v1/catalog — قراءة ما هو محفوظ عندنا
| معامل | الوصف |
|---|---|
kind | product · service (افتراضيًا كلاهما) |
source | عناصر مصدر معيّن فقط. العناصر اليدوية لها "source": null |
status | active (افتراضي) · archived · all |
updated_since | ISO 8601 — ما تغيّر بعد هذا الوقت |
limit · cursor | 1–200 (افتراضي 100) وترقيم بالمؤشر كما في العملاء |
{ "ok": true,
"data": [ { "id": "…", "kind": "product", "source": "main-pos", "external_id": "P-1001",
"name": "شامبو مرطّب 250 مل", "description": "", "category": "عناية بالشعر",
"price": "45.00", "price_minor": 4500, "currency": "SAR", "sku": "SH-250", "barcode": "6281000101011",
"image_url": "https://cdn.your-pos.com/img/sh-250.jpg", "status": "active", "sort": 1,
"cost_minor": 2000, "vat_rate": 15, "track_stock": false,
"updated_at": "2026-09-22T10:00:00Z", "last_synced_at": "2026-09-22T10:00:00Z" } ],
"meta": { "limit": 100, "count": 1, "next_cursor": null, "has_more": false } }
image_url في القراءة هو الرابط الفعلي للصورة: الرابط الخارجي إن وُجد، وإلا رابط الصورة المرفوعة يدويًا، وإلا null (طبّق صورتك الافتراضية في نظامك).
الجلب (Pull): ملف كاتالوج ننسخه نحن
انشر على رابط HTTPS ثابت وعام (يجوز حمايته بترويسة مثل Authorization: Bearer … تُحفظ عندنا مشفرة) مستندًا بهذا الشكل — نفس حقول العنصر أعلاه، إما داخل items أو كمصفوفة مباشرة:
GET https://pos.example.com/retention/catalog.json
Content-Type: application/json
{ "version": "1", "generated_at": "2026-09-22T10:00:00Z",
"items": [
{ "external_id": "P-1001", "kind": "product", "name": "شامبو مرطّب 250 مل", "price": 45.00,
"category": "عناية بالشعر", "sku": "SH-250", "image_url": "https://cdn.your-pos.com/img/sh-250.jpg" },
{ "external_id": "S-77", "kind": "service", "name": "قص وتصفيف", "price": 80, "duration_min": 45 }
] }
- الحد 5 ميجابايت و500 عنصر للملف؛ يُرفض
localhostوالعناوين الداخلية ولا نتبع التحويلات (redirects). - صاحب المنشأة يختار في اللوحة:
source(المعرّف)، الوضع (merge/replace)، ودورة الجلب (كل 1/3/6/12/24/48 ساعة أو أسبوعيًا)، ويستطيع «مزامنة الآن» في أي وقت. - الأخطاء (رد غير 2xx، JSON غير صالح، غياب
items) تُسجَّل في بطاقة المصدر مع آخر 20 محاولة، ولا تمس العناصر المحفوظة. - أفضل ممارسة: ولّد الملف من قاعدة بياناتك عند الطلب أو بمهمة مجدولة، ووفّر
ETag/Cache-Controlإن أمكن.
قواعد الصور
- رابط مباشر إلى ملف صورة (
jpg/png/webp) على https، قابل للفتح من أي متصفح بلا تسجيل دخول، لا يتغيّر مع كل طلب (لا روابط موقّعة قصيرة العمر). - الموصى به: مربعة 600×600 أو أكبر، أقل من 300 كيلوبايت. نحن لا نغيّر حجمها ولا ننسخها؛ نعرضها كما هي في اللوحة، شاشة الكاشير، صفحة الحجز، وصفحة الشريك.
- الصور تُعرض فقط إذا فعّل صاحب المنشأة الإعدادات ← عرض الكاتالوج ← «مع الصور»؛ في وضع النصوص تُحفظ الروابط ولا تُعرض. العنصر بلا صورة يأخذ الصورة الافتراضية للمنشأة.
- إن تعطّل رابط لاحقًا يعرض المتصفح الصورة الافتراضية تلقائيًا؛ أرسل الرابط الجديد في المزامنة التالية.
ما يحدث بعد المزامنة
- العنصر يظهر فورًا في المنتجات/الخدمات بوسم «مزامَن: source»، وفي شاشة الكاشير، وفي صفحة الحجز (الخدمات المتاحة إلكترونيًا) وصفحة الشريك.
- تعديل صاحب المنشأة اليدوي على حقل مزامَن (الاسم، السعر…) يُستبدل عند المزامنة التالية — الحقول المحلية تبقى. من الأفضل أن يعدّل في نظامك ثم يزامن.
- كل دفعة تسجّل حدث
catalog.synced(يصل إلى الويب هوك إن اشتركت فيه) بملخص الأعداد. - المخزون ليس جزءًا من المزامنة عمدًا: يبقى في نظامك، وعناصرك تأتي بـ
track_stock: falseما لم ترسل غير ذلك.
قائمة مزوّد الكاتالوج قبل الإطلاق
external_idثابت لا يتغيّر بتغيّر الاسم أو السعر، وsourceواحد ثابت لكل نظام.- ترسل القائمة كاملة بـ
replace(أو تنشر ملف الجلب كاملًا) لتختفي العناصر المتوقفة من الكاشير تلقائيًا. - تعيد المحاولة بأمان: الطلب نفسه مرتين لا ينشئ تكرارًا (البصمة تتجاهل ما لم يتغيّر).
- روابط الصور https مباشرة وثابتة، وتختبرها من متصفح خارج شبكتك.
- تراقب
failed/errorsفي الرد (أو بطاقة المصدر في اللوحة) وتصلح العناصر المرفوضة في المصدر. - عملة كل عنصر هي عملة المنشأة (أو لا ترسلها أصلًا).
الأخطاء والحدود
{ "error": { "code": "validation_failed", "message": "تعذر التحقق من الطلب.",
"request_id": "req_…", "fields": { "phone": ["يجب أن يكون رقم هاتف صالحًا"] } } }
| HTTP | code | المعنى |
|---|---|---|
| 401 | invalid_api_key | المفتاح غير صالح أو محذوف |
| 403 | insufficient_scope · feature_not_available · subscription_inactive | النطاق لا يكفي / الباقة بلا API (أو نوع عنصر الكاتالوج خارج الباقة) / الاشتراك متوقف |
| 400 | idempotency_key_required | ترويسة التكرار ناقصة |
| 404 | not_found | المسار أو العميل غير موجود |
| 409 | idempotency_conflict | المفتاح نفسه ببيانات مختلفة |
| 422 | validation_failed | حقول غير صالحة — التفاصيل في fields |
| 429 | rate_limited | أكثر من 240 طلبًا في الدقيقة للمفتاح — انتظر ثم أعد |
أرفق request_id عند التواصل مع الدعم.
الويب هوك (إشعارات صادرة)
من الصفحة نفسها اضغط + Webhook: رابط HTTPS في نظامك + الأحداث التي تريدها (فارغ = كل الأحداث). يظهر السر whsec_… مرة واحدة — احفظه للتحقق من التوقيع.
كل حدث يُرسل بطلب POST بهذا الشكل:
POST https://your-system.example/hooks/retention
Content-Type: application/json
User-Agent: RetentionOS-Webhook/1.0
X-Retention-Event: transaction.completed
X-Retention-Event-Id: evt_…
X-Retention-Signature: sha256=3f1a…
{ "event": "transaction.completed", "timestamp": "2026-09-22T14:05:00Z",
"data": { "transaction_id": "…", "customer_id": "cust_…", "amount_minor": 18000, "gross_minor": 18000, "discount_minor": 0, "currency": "SAR" },
"meta": { "event_id": "evt_…", "correlation_id": null } }
- ردّ بـ 2xx خلال ثوانٍ ثم عالج الحدث في الخلفية. أي ردّ آخر يُعاد إرساله تلقائيًا: بعد دقيقة، 5 دقائق، 15، ساعة، 6 ساعات (5 محاولات).
- ردّ
4xx(عدا 408/429) يعني أن نظامك رفض الحدث فلا يُعاد؛ يظهر «فاشلة نهائيًا» في اللوحة مع زر إعادة الإرسال. - الأحداث قد تصل أكثر من مرة — اجعل المعالجة آمنة للتكرار باستخدام
event_id. - الرابط يجب أن يكون HTTPS عامًا (لا localhost ولا عناوين داخلية).
جدول الأحداث
| الحدث | متى | أهم حقول data |
|---|---|---|
customer.created | تسجيل عميل (QR، واي فاي، كاشير، API) | customer_id, display_name |
customer.merged | دمج عميلين | source_customer_id, target_customer_id |
transaction.completed | عملية مسجلة (كاشير/يدوي/API) | transaction_id, customer_id, amount_minor, gross_minor, discount_minor, currency |
transaction.voided | إلغاء عملية | transaction_id, customer_id |
sale.paid / sale.refunded | بيع من كاشير المنصة / استرداد | number, total_minor, customer_id, invoice_id / amount_minor, full |
reward.redeemed | استبدال مكافأة | customer_id, reward_id, transaction_id |
loyalty.adjusted | تعديل نقاط يدوي | customer_id, delta |
tier.changed | تغيّر مستوى العميل | customer_id, to, direction |
coupon.issued / coupon.used | إصدار كوبون / استخدامه | customer_id, coupon_id, code / transaction_id, discount_minor |
referral.joined / referral.qualified / referral.rewarded | صديق انضم / اشترى / مكافأة المُحيل | referrer_customer_id, referee_customer_id / transaction_id / grant_type |
review.rated | تقييم بعد الزيارة | customer_id, rating, positive |
appointment.created / appointment.rescheduled / appointment.canceled | الحجوزات | appointment_id, customer_id, starts_at, source / by |
catalog.synced | دفعة مزامنة كاتالوج (دفع أو جلب) | source, mode, received, created, updated, unchanged, archived, failed |
game.won | جائزة من عجلة الحظ | customer_id, prize, kind |
التحقق من التوقيع
التوقيع = sha256= + HMAC-SHA256 لجسم الطلب الخام بالسر whsec_…. تحقق قبل أي معالجة:
// PHP
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_RETENTION_SIGNATURE'] ?? '')) { http_response_code(401); exit; }
$event = json_decode($raw, true);
http_response_code(200); // ثم عالج $event في الخلفية
// Node.js (Express — استخدم الجسم الخام لا المُحلَّل)
app.post('/hooks/retention', express.raw({ type: '*/*' }), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');
const given = req.get('X-Retention-Signature') || '';
if (expected.length !== given.length || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given))) return res.sendStatus(401);
const event = JSON.parse(req.body);
res.sendStatus(200); // ثم عالج event
});
قبل الإطلاق
- مفتاح API منفصل لكل نظام (كاشير، متجر، ERP) بأقل نطاقات تكفيه؛ يمكن إيقاف أي مفتاح من اللوحة دون أن يتأثر غيره.
- رقم الفاتورة هو
Idempotency-Keyوexternal_transaction_idمعًا. - سجّل العميل أولًا (أو ابحث عنه بالجوال) ثم أرسل العملية بمعرّفه.
- الكاتالوج يأتي من نظامك بـ
PUT /catalog/syncأو ملف جلب، بـexternal_idثابت وروابط صور https — لا إدخال يدوي مزدوج. - معالِج الويب هوك يرد 2xx فورًا ويتجاهل الأحداث المكررة بـ
event_id. - راقب صفحة «API و Webhooks»: آخر استخدام لكل مفتاح، وحالة تسليم كل ويب هوك، وإعادة الإرسال عند الفشل.
الدعم الفني للتكامل: تواصل معنا مع request_id أو event_id.