للمطورين

واجهة API والتكاملات في دورني

اربط دورني بنظام الكاشير وموقعك ونظام إدارة العملاء وأدوات الأتمتة: نقاط ولاء مع كل عملية بيع، عملاء متزامنون، هدايا يتم التحقق منها عند الكاونتر، وأحداث تصل إلى خادمك فوراً.

  • واجهة REST بصيغة JSON (v1)
  • مفاتيح API بصلاحيات
  • Webhooks موقّعة HMAC-SHA256
  • باقات بريميوم والمؤسسات
Base URLhttps://app.dawwerni.com/api/ext/v1

ماذا يمكنك أن تبني

ست تكاملات تعمل اليوم، دون أي تطوير خاص من جهة دورني.

البدء السريع

ثلاث خطوات لأول طلب. حوالي خمس دقائق.

  1. 1
    فعّل التكاملات

    التكاملات (API وWebhooks والكاشير) متوفرة في باقات بريميوم والمؤسسات. صاحب الحساب هو من يدير المفاتيح.

  2. 2
    أنشئ مفتاح API

    سمّه، واختر صلاحياته وإن أردت نشاطاً تجارياً واحداً. المفتاح الكامل (dw_live_…) يظهر مرة واحدة فقط: احفظه على خادمك.

  3. 3
    أرسل أول طلب

    GET /me يعيد اسم المفتاح وصلاحياته والأنشطة التجارية التي يصل إليها.

داخل التطبيقالقائمةالتكاملاتمفاتيح APIإنشاء مفتاح

أول طلب · cURL
curl https://app.dawwerni.com/api/ext/v1/me \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "apiVersion": "v1",
  "key": {
    "id": 7,
    "name": "POS bridge - Tunis",
    "prefix": "dw_live_a1b2c3d4",
    "scopes": [
      "customers:read",
      "customers:write",
      "points:write",
      "redemptions:write",
      "rewards:read",
      "webhooks:manage",
      "winnings:verify"
    ],
    "businessScope": null
  },
  "businesses": [
    {
      "id": 186,
      "name": "Café Amira"
    }
  ],
  "rateLimitPerMinute": 120
}

المصادقة

كل طلب يحمل مفتاح API في ترويسة Authorization (Bearer) أو في X-Api-Key. كل شيء عبر HTTPS وبصيغة JSON.

HTTP headers
Authorization: Bearer dw_live_a1b2c3d4••••••••••••••••••••••••••••••••••••••••
# or
X-Api-Key: dw_live_a1b2c3d4••••••••••••••••••••••••••••••••••••••••
  • استخدم المفتاح على خوادمك فقط: الواجهة لا تقبل الطلبات من المتصفح (CORS)، والمفتاح المنشور في موقع أو تطبيق جوال يصبح مكشوفاً.
  • المفتاح يعمل فقط ضمن صلاحياته وأنشطته التجارية؛ وإلا تجيب الواجهة بـ 403.
  • إذا كان المفتاح يغطي عدة أنشطة تجارية، أرسل businessId في الطلب (وإلا 400 BUSINESS_REQUIRED).
  • المفتاح الملغى يتوقف فوراً (401 API_KEY_REVOKED). حتى 20 مفتاحاً نشطاً لكل حساب.

الصلاحيات (scopes)

الصلاحيةتسمح بـ
customers:readالبحث عن العملاء وقراءة نقاطهم
customers:writeإنشاء العملاء أو تحديثهم
points:writeتسجيل المبيعات ومنح النقاط
rewards:readعرض مكافآت الولاء
redemptions:writeاستبدال النقاط بمكافأة
winnings:verifyالتحقق من الهدايا وتسليمها
webhooks:manageإدارة عناوين Webhooks

القواعد العامة

العنوان الأساسي
https://app.dawwerni.com/api/ext/v1 — صيغة JSON فقط (Content-Type: application/json في طلبات POST).
التواريخ
بصيغة ISO 8601. حقول حساب العميل (createdAt وupdatedAt) بالتوقيت المحلي دون منطقة زمنية؛ والبقية (المفاتيح وWebhooks والسجلات) بتوقيت UTC مع «Z».
أرقام الهاتف
يُنصح بالصيغة الدولية (+21698123456). نقبل «00216…» والمسافات؛ يتم التعرف على العميل بالبريد أولاً ثم بالهاتف.
التقسيم إلى صفحات
القوائم تقبل page (من 0) وsize (20 افتراضياً، 100 كحد أقصى) وتعيد data وpage.
عدم التكرار (Idempotency)
أضف ترويسة Idempotency-Key (مثلاً UUID) لكل طلب POST. إعادة نفس الطلب خلال 24 ساعة تعيد الاستجابة الأصلية مع Idempotent-Replayed: true دون تنفيذ العملية مرة أخرى.
حد الطلبات
120 طلباً في الدقيقة لكل مفتاح. بعد ذلك: HTTP 429 مع ترويسة Retry-After.
الإصدارات
الإصدار v1 يتلقى إضافات فقط (حقول ونقاط وصول جديدة). تجاهل الحقول غير المعروفة في الاستجابات.
شكل القوائم
{
  "data": [ … ],
  "page": { "number": 0, "size": 20, "totalElements": 42, "totalPages": 3, "hasNext": true }
}
ترويسات حد الطلبات
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 119
# when exceeded: HTTP 429 + Retry-After: <seconds>

نقاط الوصول (Endpoints)

كل الاستجابات أدناه ناتجة عن طلبات حقيقية للواجهة على مقهى تجريبي في تونس.

معلومات المفتاح

GET/api/ext/v1/me

الصلاحية: أي مفتاح صالح

اسم المفتاح وبادئته وصلاحياته، والأنشطة التجارية التي يصل إليها، وحد الطلبات. مفيد لاختبار إعداداتك.

curl "https://app.dawwerni.com/api/ext/v1/me" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "apiVersion": "v1",
  "key": {
    "id": 7,
    "name": "POS bridge - Tunis",
    "prefix": "dw_live_a1b2c3d4",
    "scopes": [
      "customers:read",
      "customers:write",
      "points:write",
      "redemptions:write",
      "rewards:read",
      "webhooks:manage",
      "winnings:verify"
    ],
    "businessScope": null
  },
  "businesses": [
    {
      "id": 186,
      "name": "Café Amira"
    }
  ],
  "rateLimitPerMinute": 120
}

البحث عن العملاء

GET/api/ext/v1/customers

الصلاحية: customers:read

قائمة مقسّمة لحسابات الولاء. صفِّ حسب الهاتف أو البريد؛ دون تصفية: كل عملاء الأنشطة التجارية للمفتاح.

المعاملات
الاسمالمكانالنوعالوصف
phonequerystringهاتف العميل (رمّز + بـ %2B) (اختياري)
emailquerystringبريد العميل (اختياري)
businessIdqueryintegerحصر النتائج في نشاط تجاري واحد (اختياري)
pagequeryinteger ≥ 0رقم الصفحة من 0 (اختياري)
sizequeryinteger 1–100 (20)حجم الصفحة (اختياري)
curl "https://app.dawwerni.com/api/ext/v1/customers?phone=%2B21698123456" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "data": [
    {
      "id": 313,
      "businessId": 186,
      "name": "Amira B.",
      "email": "[email protected]",
      "phone": "+21698123456",
      "points": {
        "spendable": 0,
        "locked": 0,
        "lifetimeEarned": 0
      },
      "createdAt": "2026-10-13T21:36:40.087382",
      "updatedAt": "2026-10-13T21:36:40.121926"
    }
  ],
  "page": {
    "number": 0,
    "size": 20,
    "totalElements": 1,
    "totalPages": 1,
    "hasNext": false
  }
}

قراءة عميل

GET/api/ext/v1/customers/313

الصلاحية: customers:read

حساب ولاء واحد بمعرّفه في دورني.

المعاملات
الاسمالمكانالنوعالوصف
id*pathintegerمعرّف العميل في دورني (إلزامي)
curl "https://app.dawwerni.com/api/ext/v1/customers/313" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "id": 313,
  "businessId": 186,
  "name": "Amira B.",
  "email": "[email protected]",
  "phone": "+21698123456",
  "points": {
    "spendable": 0,
    "locked": 0,
    "lifetimeEarned": 0
  },
  "createdAt": "2026-10-13T21:36:40.087382",
  "updatedAt": "2026-10-13T21:36:40.121926"
}
404 Not Found الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "error": {
    "code": "CUSTOMER_NOT_FOUND",
    "message": "Customer not found"
  }
}

إنشاء عميل أو تحديثه

POST/api/ext/v1/customers

الصلاحية: customers:write

يبحث عن العميل بالبريد ثم بالهاتف؛ ينشئه إن لم يوجد (201) أو يكمل بياناته (200).

المعاملات
الاسمالمكانالنوعالوصف
phonebodystring (E.164)الهاتف؛ الهاتف أو البريد إلزامي (اختياري)
emailbodystringالبريد؛ الهاتف أو البريد إلزامي (اختياري)
namebodystring ≤ 120الاسم المعروض (اختياري)
businessIdbodyintegerإلزامي إذا كان المفتاح يغطي عدة أنشطة (اختياري)
Idempotency-Keyheaderstring ≤ 255إعادة آمنة لمدة 24 ساعة (اختياري)
curl -X POST "https://app.dawwerni.com/api/ext/v1/customers" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Amira Ben Salah", "phone": "+21698123456", "email": "[email protected]" }'
201 Created الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "id": 313,
  "businessId": 186,
  "name": "Amira Ben Salah",
  "email": "[email protected]",
  "phone": "+21698123456",
  "points": {
    "spendable": 0,
    "locked": 0,
    "lifetimeEarned": 0
  },
  "createdAt": "2026-10-13T21:36:40.0873817",
  "updatedAt": "2026-10-13T21:36:40.0873817"
}
200 OK الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "id": 313,
  "businessId": 186,
  "name": "Amira B.",
  "email": "[email protected]",
  "phone": "+21698123456",
  "points": {
    "spendable": 0,
    "locked": 0,
    "lifetimeEarned": 0
  },
  "createdAt": "2026-10-13T21:36:40.087382",
  "updatedAt": "2026-10-13T21:36:40.1219263"
}
400 Bad Request الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "error": {
    "code": "CUSTOMER_IDENTIFIER_REQUIRED",
    "message": "Provide a phone or an email"
  }
}

رصيد النقاط

GET/api/ext/v1/customers/313/points

الصلاحية: customers:read

النقاط المتاحة والمجمّدة والمكتسبة إجمالاً، مقسّمة حسب البرنامج (الألعاب / الولاء) وبطاقة الأختام.

المعاملات
الاسمالمكانالنوعالوصف
id*pathintegerمعرّف العميل في دورني (إلزامي)
curl "https://app.dawwerni.com/api/ext/v1/customers/313/points" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "customerId": 313,
  "businessId": 186,
  "total": 192,
  "spendable": 192,
  "locked": 0,
  "lifetimeEarned": 192,
  "byProgram": {
    "GAMES": 0,
    "LOYALTY": 192
  },
  "balanceMode": "COMBINED",
  "loyaltyMode": "POINTS",
  "stampCount": 0,
  "stampsRequired": 10,
  "stampRewardsAvailable": 0
}

تسجيل عملية شراء (نقاط)

POST/api/ext/v1/purchases

الصلاحية: points:write

يسجّل إيصال الكاشير ويضيف النقاط حسب قواعد برنامج الولاء. يتم إنشاء العميل إن لم يكن موجوداً.

  • externalId يعرّف الإيصال: إعادة إرسال نفس externalId تعيد النتيجة الأولى مع "duplicate": true دون منح أي نقاط.
  • دون "points" يتم تحويل المبلغ حسب قاعدة بطاقة الولاء (نقاط لكل وحدة مدفوعة). فعّل بطاقة الولاء من قسم الولاء؛ وإلا تكون pointsAwarded صفراً.
  • المرتجعات لا تسحب نقاطاً: أرسل عمليات البيع فقط.
المعاملات
الاسمالمكانالنوعالوصف
externalId*bodystring ≤ 120معرّف فريد للإيصال في الكاشير (إلزامي)
amount*bodynumber ≥ 0المبلغ المدفوع (إلزامي)
customer*body{ id | phone | email, name }العميل: معرّف دورني، أو الهاتف / البريد (+ الاسم) (إلزامي)
currencybodyISO 4217 (TND, EGP, SAR…)رمز العملة ISO (TND وEGP وSAR وUSD…) (اختياري)
pointsbodyinteger 0–1 000 000نقاط محددة (وإلا تُحسب بقاعدتك) (اختياري)
itemsbody[{ sku, name, quantity, unitPrice }]بنود الإيصال (يُحفظ عددها فقط) (اختياري)
businessIdbodyintegerإلزامي إذا كان المفتاح يغطي عدة أنشطة (اختياري)
Idempotency-Keyheaderstring ≤ 255إعادة آمنة لمدة 24 ساعة (اختياري)
curl -X POST "https://app.dawwerni.com/api/ext/v1/purchases" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: f3b0c442-98fc-4c1c-9a3a-6b0e5a1d2c01" \
  -d '{ "customer": { "phone": "+21698123456", "name": "Amira Ben Salah" }, "amount": 42.5, "currency": "TND", "externalId": "R-2026-000123", "items": [ { "sku": "LATTE", "name": "Latte", "quantity": 2, "unitPrice": 7.5 } ] }'
201 Created الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "purchaseId": 9,
  "externalId": "R-2026-000123",
  "duplicate": false,
  "businessId": 186,
  "amount": 42.5,
  "currency": "TND",
  "itemsCount": 1,
  "pointsRequested": null,
  "pointsAwarded": 42,
  "customer": {
    "id": 313,
    "businessId": 186,
    "name": "Amira Ben Salah",
    "email": "[email protected]",
    "phone": "+21698123456",
    "points": {
      "spendable": 42,
      "locked": 0,
      "lifetimeEarned": 42
    },
    "createdAt": "2026-10-13T21:36:40.087382",
    "updatedAt": "2026-10-13T21:36:40.2972964"
  },
  "balance": {
    "customerId": 313,
    "businessId": 186,
    "total": 42,
    "spendable": 42,
    "locked": 0,
    "lifetimeEarned": 42,
    "byProgram": {
      "GAMES": 0,
      "LOYALTY": 42
    },
    "balanceMode": "COMBINED",
    "loyaltyMode": "POINTS",
    "stampCount": 0,
    "stampsRequired": 10,
    "stampRewardsAvailable": 0,
    "program": "LOYALTY",
    "outcome": "OK"
  },
  "source": "api"
}
200 OK الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "purchaseId": 9,
  "externalId": "R-2026-000123",
  "duplicate": true,
  "businessId": 186,
  "amount": 42.5,
  "currency": "TND",
  "itemsCount": 1,
  "pointsRequested": null,
  "pointsAwarded": 0,
  "customer": {
    "id": 313,
    "businessId": 186,
    "name": "Amira Ben Salah",
    "email": "[email protected]",
    "phone": "+21698123456",
    "points": {
      "spendable": 42,
      "locked": 0,
      "lifetimeEarned": 42
    },
    "createdAt": "2026-10-13T21:36:40.087382",
    "updatedAt": "2026-10-13T21:36:40.2972964"
  },
  "balance": {
    "customerId": 313,
    "businessId": 186,
    "total": 42,
    "spendable": 42,
    "locked": 0,
    "lifetimeEarned": 42,
    "byProgram": {
      "GAMES": 0,
      "LOYALTY": 42
    },
    "balanceMode": "COMBINED",
    "loyaltyMode": "POINTS",
    "stampCount": 0,
    "stampsRequired": 10,
    "stampRewardsAvailable": 0,
    "program": "LOYALTY",
    "outcome": "OK"
  },
  "source": "api"
}
400 Bad Request الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "error": {
    "code": "EXTERNAL_ID_REQUIRED",
    "message": "externalId (POS receipt id, max 120 chars) is required"
  }
}

عرض المكافآت

GET/api/ext/v1/rewards

الصلاحية: rewards:read

المكافآت النشطة لأنشطتك: تكلفتها بالنقاط والمخزون والبرنامج الذي يمكن الدفع به.

المعاملات
الاسمالمكانالنوعالوصف
businessIdqueryintegerحصر النتائج في نشاط تجاري واحد (اختياري)
pagequeryinteger ≥ 0رقم الصفحة من 0 (اختياري)
sizequeryinteger 1–100 (20)حجم الصفحة (اختياري)
curl "https://app.dawwerni.com/api/ext/v1/rewards" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "data": [
    {
      "id": 34,
      "businessId": 186,
      "name": "Free cappuccino",
      "description": "One cappuccino, any size",
      "pointsCost": 100,
      "stock": 50,
      "program": "BOTH"
    }
  ],
  "page": {
    "number": 0,
    "size": 20,
    "totalElements": 1,
    "totalPages": 1,
    "hasNext": false
  }
}

استبدال مكافأة

POST/api/ext/v1/redemptions

الصلاحية: redemptions:write

يخصم النقاط ويُنقص المخزون وينشئ عملية استبدال «قيد الانتظار» تظهر في صفحات الولاء.

  • يجب أن تكون للعميل مشاركة واحدة على الأقل في لعبة (وإلا 422 NO_PARTICIPATION_FOR_CUSTOMER).
  • مع externalId، إعادة إرسال نفس الاستبدال لا تكرره ("duplicate": true).
المعاملات
الاسمالمكانالنوعالوصف
customerId*bodyintegerمعرّف العميل في دورني (إلزامي)
rewardId*bodyintegerمعرّف المكافأة (GET /rewards) (إلزامي)
externalIdbodystring ≤ 120مرجعك، يمنع التكرار (اختياري)
notesbodystring ≤ 250ملاحظة داخلية (اختياري)
Idempotency-Keyheaderstring ≤ 255إعادة آمنة لمدة 24 ساعة (اختياري)
curl -X POST "https://app.dawwerni.com/api/ext/v1/redemptions" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "customerId": 313, "rewardId": 33, "externalId": "RD-1001", "notes": "Counter 1" }'
201 Created الاستجابةمثال مبني من كود الواجهة (يتطلب مشاركة حقيقية في لعبة)
{
  "redemptionId": 57,
  "businessId": 186,
  "rewardId": 33,
  "rewardName": "Free cappuccino",
  "pointsUsed": 100,
  "status": "PENDING",
  "requestedAt": "2026-10-13T21:40:12.418302",
  "program": "LOYALTY",
  "customerId": 313,
  "externalId": "RD-1001",
  "duplicate": false
}
422 Unprocessable Entity الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "error": {
    "code": "NO_PARTICIPATION_FOR_CUSTOMER",
    "message": "This customer has no participation yet; redemptions through the API need at least one"
  }
}

التحقق من هدية

POST/api/ext/v1/winnings/verify

الصلاحية: winnings:verify

مثل مسح رمز QR في التطبيق: يتحقق من الرمز، ويسجّل الهدية كمسلّمة ويعيد بيانات الفائز.

  • code يقبل رمز QR، أو الرابط الكامل /verify-winning/…، أو المرجع من 8 أحرف الموجود في بريد الفائز.
  • هدية سبق تسليمها تعيد 200 مع "verified": false و"alreadyClaimed": true.
المعاملات
الاسمالمكانالنوعالوصف
code*bodystringرمز QR أو رابط التحقق أو المرجع من 8 أحرف (إلزامي)
curl -X POST "https://app.dawwerni.com/api/ext/v1/winnings/verify" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "code": "a1b2c3d4" }'
200 OK الاستجابةمثال مبني من كود الواجهة (يتطلب مشاركة حقيقية في لعبة)
{
  "verified": true,
  "alreadyClaimed": false,
  "message": "Winning verified successfully",
  "winning": {
    "id": 9120,
    "ref": "a1b2c3d4",
    "businessId": 186,
    "gameId": 412,
    "prize": "Free croissant",
    "fullName": "Amira Ben Salah",
    "email": "[email protected]",
    "phone": "+21698123456",
    "status": "taked",
    "createdAt": "2026-10-12T17:05:44.120931"
  }
}
404 Not Found الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "error": {
    "code": "WINNING_NOT_FOUND",
    "message": "No gift matches this code"
  }
}
400 Bad Request الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "error": {
    "code": "INVALID_CODE",
    "message": "The code is not a valid Dawwerni gift code"
  }
}

عرض Webhooks

GET/api/ext/v1/webhooks

الصلاحية: webhooks:manage

عناوينك وأحداثها وحالة التسليم.

المعاملات
الاسمالمكانالنوعالوصف
pagequeryinteger ≥ 0رقم الصفحة من 0 (اختياري)
sizequeryinteger 1–100 (20)حجم الصفحة (اختياري)
curl "https://app.dawwerni.com/api/ext/v1/webhooks" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "data": [
    {
      "id": 2,
      "url": "https://example.com/webhooks/dawwerni",
      "description": "CRM sync",
      "events": [
        "winning.created",
        "points.earned"
      ],
      "active": false,
      "failureCount": 0,
      "lastSuccessAt": null,
      "lastFailureAt": null,
      "disabledAt": null,
      "disabledReason": null,
      "secretHint": "whsec_...9fQe",
      "createdAt": "2026-10-13T20:36:40.854516Z"
    }
  ],
  "page": {
    "number": 0,
    "size": 20,
    "totalElements": 1,
    "totalPages": 1,
    "hasNext": false
  }
}

إنشاء Webhook

POST/api/ext/v1/webhooks

الصلاحية: webhooks:manage

يسجّل عنوان HTTPS عاماً والأحداث المطلوبة. الاستجابة تحتوي على مفتاح التوقيع (ويظهر لاحقاً في التطبيق أيضاً).

  • HTTPS فقط؛ العناوين الخاصة أو المحلية مرفوضة. حتى 10 عناوين لكل حساب.
المعاملات
الاسمالمكانالنوعالوصف
url*bodyhttps URLعنوان HTTPS عام لخادمك (إلزامي)
events*bodystring[]الأحداث المطلوبة (انظر Webhooks) (إلزامي)
descriptionbodystring ≤ 255وصف حر (اختياري)
activebodyboolean (true)false لإنشائه متوقفاً (اختياري)
curl -X POST "https://app.dawwerni.com/api/ext/v1/webhooks" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://crm.example.com/webhooks/dawwerni", "events": [ "points.earned", "winning.created" ], "description": "CRM sync" }'
201 Created الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "id": 2,
  "url": "https://example.com/webhooks/dawwerni",
  "description": "CRM sync",
  "events": [
    "winning.created",
    "points.earned"
  ],
  "active": false,
  "failureCount": 0,
  "lastSuccessAt": null,
  "lastFailureAt": null,
  "disabledAt": null,
  "disabledReason": null,
  "secretHint": "whsec_...9fQe",
  "createdAt": "2026-10-13T20:36:40.854516100Z",
  "secret": "whsec_<shown once: store it in your server config>"
}
400 Bad Request الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "error": {
    "code": "INVALID_WEBHOOK_URL",
    "message": "Only https:// URLs are allowed"
  }
}
400 Bad Request الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "error": {
    "code": "UNKNOWN_EVENT",
    "message": "Unknown event: order.paid"
  }
}

حذف Webhook

DELETE/api/ext/v1/webhooks/2

الصلاحية: webhooks:manage

يحذف العنوان ويلغي عمليات التسليم المعلّقة.

المعاملات
الاسمالمكانالنوعالوصف
id*pathintegerمعرّف العنوان (إلزامي)
curl -X DELETE "https://app.dawwerni.com/api/ext/v1/webhooks/2" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
204 No Content الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)

رموز الأخطاء

كل الأخطاء بنفس الشكل. اعتمد على الرمز (ثابت)؛ الرسالة بالإنجليزية وقد تتغير.

403 Forbiddenاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "error": {
    "code": "INSUFFICIENT_SCOPE",
    "message": "This API key is missing the scope points:write"
  }
}
HTTPالرمزالمعنى
400VALIDATION_FAILEDمعامل ناقص أو غير صحيح
400INVALID_JSONالمحتوى ليس JSON صالحاً
400CUSTOMER_IDENTIFIER_REQUIREDالهاتف أو البريد مطلوب
400INVALID_PHONEهاتف غير صالح (يُنصح بالصيغة الدولية)
400INVALID_EMAILبريد غير صالح
400EXTERNAL_ID_REQUIREDexternalId ناقص (120 حرفاً كحد أقصى)
400INVALID_AMOUNTالمبلغ ناقص أو سالب
400INVALID_CURRENCYالعملة ليست رمز ISO 4217
400INVALID_POINTSpoints خارج النطاق من 0 إلى 1٬000٬000
400BUSINESS_REQUIREDالمفتاح يغطي عدة أنشطة: أرسل businessId
400INVALID_IDEMPOTENCY_KEYIdempotency-Key: من 1 إلى 255 حرف ASCII
400INVALID_EXTERNAL_IDexternalId طويل جداً
400CODE_REQUIREDcode ناقص
400INVALID_CODEليس رمز هدية من دورني
400INVALID_WEBHOOK_URLالعنوان مرفوض (HTTPS عام فقط)
400EVENTS_REQUIREDاختر حدثاً واحداً على الأقل
400UNKNOWN_EVENTحدث غير معروف
401UNAUTHENTICATEDلم يتم إرسال مفتاح API
401INVALID_API_KEYمفتاح API غير صالح
401API_KEY_REVOKEDمفتاح API ملغى
403ACCOUNT_INACTIVEحساب دورني غير نشط
403FEATURE_NOT_ENABLEDالتكاملات غير مشمولة في الباقة
403INSUFFICIENT_SCOPEالمفتاح ينقصه صلاحية
403BUSINESS_FORBIDDENالنشاط التجاري خارج نطاق المفتاح
403BUSINESS_UNAVAILABLEتم حذف النشاط التجاري للمفتاح
404CUSTOMER_NOT_FOUNDالعميل غير موجود
404REWARD_NOT_FOUNDالمكافأة غير موجودة
404WINNING_NOT_FOUNDلا توجد هدية بهذا الرمز
404WEBHOOK_NOT_FOUNDعنوان Webhook غير موجود
409WINNING_NOT_CLAIMABLEمشاركة دون هدية (خاسرة أو ملغاة)
409IDEMPOTENCY_CONFLICTنفس Idempotency-Key قيد المعالجة: أعد المحاولة
409DUPLICATE_EXTERNAL_IDنفس externalId قيد المعالجة: أعد المحاولة
415UNSUPPORTED_MEDIA_TYPEأرسل Content-Type: application/json
422IDEMPOTENCY_KEY_REUSEDIdempotency-Key مستخدم لطلب آخر
422INSUFFICIENT_POINTSالنقاط غير كافية
422REWARD_INACTIVEالمكافأة معطّلة
422REWARD_OUT_OF_STOCKالمكافأة نفدت
422NO_PARTICIPATION_FOR_CUSTOMERالعميل لم يلعب أي لعبة بعد
422REDEMPTION_REJECTEDمرفوض حسب قواعد الولاء
422WEBHOOK_LIMIT_REACHEDحتى 10 عناوين
422NO_BUSINESSلا يوجد نشاط تجاري نشط في الحساب
429RATE_LIMITEDطلبات كثيرة: انتظر Retry-After ثانية
500INTERNAL_ERRORخطأ غير متوقع: أعد المحاولة لاحقاً

Webhooks

يرسل دورني طلب POST موقّعاً عبر HTTPS إلى خادمك عند كل حدث تختاره. أجب بأي حالة 2xx خلال 5 ثوانٍ. يعمل مع أي أداة تستقبل Webhooks (خادمك، Make، n8n، Zapier…).

داخل التطبيقالقائمةالتكاملاتخطافات الويبإضافة عنوان استقبال

الأحداث

الحدثيُرسل عندما
participant.createdيترك لاعب بياناته في لعبة (فائزاً أم لا)
winning.createdيربح لاعب هدية
winning.claimedتُسلَّم هدية (مسح QR، تغيير الحالة أو /winnings/verify)
points.earnedتُضاف نقاط من عملية شراء وصلت عبر الواجهة أو من كاشير مربوط
reward.redeemedتُستبدل مكافأة عبر الواجهة

مثال على المحتوى

{
  "id": "evt_8f2k4m9q1x7w3z5r0t6y2u4p",
  "type": "points.earned",
  "createdAt": "2026-10-13T20:36:40.512Z",
  "apiVersion": "v1",
  "data": {
    "customerId": 313,
    "businessId": 186,
    "points": 42,
    "reason": "purchase",
    "externalId": "R-2026-000123",
    "amount": 42.5,
    "currency": "TND",
    "source": "api",
    "balance": {
      "customerId": 313,
      "businessId": 186,
      "total": 42,
      "spendable": 42,
      "locked": 0,
      "lifetimeEarned": 42,
      "byProgram": {
        "GAMES": 0,
        "LOYALTY": 42
      },
      "program": "LOYALTY",
      "outcome": "OK"
    }
  }
}

مثال مبني من كود الواجهة (يتطلب مشاركة حقيقية في لعبة)

الترويسات والتوقيع

HTTP
POST https://crm.example.com/webhooks/dawwerni
Content-Type: application/json; charset=utf-8
User-Agent: Dawwerni-Webhooks/1.0
X-Dawwerni-Event: points.earned
X-Dawwerni-Delivery: dlv_4k9x2m7q1w8z5r3t6y0u2p4s     (same value on every retry)
X-Dawwerni-Timestamp: 1791927400                    (unix seconds)
X-Dawwerni-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + "." + raw body)>

التحقق من التوقيع

احسب HMAC-SHA256(secret, timestamp + "." + المحتوى الخام) بصيغة hex مسبوقاً بـ «sha256=»، وقارنه بزمن ثابت مع X-Dawwerni-Signature. ارفض الطوابع الزمنية الأقدم من 5 دقائق. زر «إرسال حدث تجريبي» في التطبيق يرسل حدث ping.

// Node.js (Express): verify on the RAW body, not on re-serialised JSON
const crypto = require('crypto');
const express = require('express');
const app = express();

app.post('/webhooks/dawwerni', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.header('X-Dawwerni-Timestamp');
  const sig = req.header('X-Dawwerni-Signature') || '';
  if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.DAWWERNI_WEBHOOK_SECRET)
    .update(ts + '.' + req.body.toString('utf8'))
    .digest('hex');
  const ok = sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  if (!ok) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString('utf8'));
  // de-duplicate on req.header('X-Dawwerni-Delivery'), then handle event.type
  res.sendStatus(200); // answer within 5 seconds, do slow work afterwards
});

إعادة المحاولة

  • الفشل = حالة غير 2xx أو تجاوز المهلة أو خطأ شبكة. إعادة المحاولة بعد دقيقة، 5 دقائق، 30 دقيقة، ساعتين ثم 12 ساعة.
  • بعد 20 فشلاً متتالياً يتم تعطيل العنوان وتصلك إشعار في التطبيق. أعد تفعيله بعد إصلاح خادمك.
  • قد يصل الحدث مرتين أو بترتيب مختلف: أزل التكرار بالاعتماد على X-Dawwerni-Delivery.
  • سجل التسليم (30 يوماً) وتغيير المفتاح السري متاحان في التطبيق.

ربط الكاشير (POS)

دون كتابة كود: لكل ربط رابط سري تلصقه في نظام الكاشير. كل عملية بيع برقم هاتف العميل أو بريده تضيف نقاطه؛ الإيصالات المكررة والمرتجعات يتم تجاهلها.

داخل التطبيقالقائمةالتكاملاتأنظمة نقاط البيعربط

  • FoodicsFOODICSالمطاعم والمقاهي في السعودية والخليج: الطلبات المغلقة المرتبطة بعميل.
  • LoyverseLOYVERSEكاشير مجاني للمقاهي والمحلات: إيصالات البيع المرتبطة بعميل (Webhook «Receipts»).
  • كاشير آخر (JSON)GENERIC_JSONأي كاشير أو متجر إلكتروني أو أداة أتمتة تستطيع إرسال طلب JSON بعد عملية البيع.

رابط الربط

الرابط يحتوي على رمز سري: من يملكه يستطيع إرسال مبيعات إلى حسابك. يمكنك استبداله في أي وقت من التطبيق (القديم يتوقف فوراً).

URL
https://app.dawwerni.com/api/ext/v1/pos/{CONNECTOR}/{connectionId}?token=pos_<secret token>
# CONNECTOR = GENERIC_JSON | LOYVERSE | FOODICS
# the token can also be sent as the header  X-Dawwerni-Token: pos_<secret token>

صيغة JSON العامة

لـ«كاشير آخر»، أرسل هذا الـJSON بعد كل عملية بيع. الحقول المعتمدة:

externalIdمعرّف فريد للإيصال (إلزامي)
amountالمبلغ المدفوع (إلزامي)
currencyرمز العملة ISO، اختياري
type"sale" (افتراضي)؛ "refund" يتم تجاهله
customerphone وemail وname: الهاتف أو البريد ضروري لكسب النقاط
itemsبنود الإيصال، اختيارية
cURL
curl -X POST "$DAWWERNI_POS_URL" \
  -H "Content-Type: application/json" \
  -d '{ "externalId": "R-2026-000200", "amount": 31.5, "currency": "TND", "type": "sale", "customer": { "phone": "+21698123456", "name": "Amira" }, "items": [ { "sku": "CROISSANT", "name": "Croissant", "quantity": 3, "unitPrice": 2.5 } ] }'
200 OK الاستجابةاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "received": 1,
  "processed": 1,
  "duplicates": 0,
  "ignored": 0,
  "failed": 0
}

عدة إيصالات دفعة واحدة

أرسل {"purchases": [...]} (حتى 100 إيصال). الاستجابة تعدّ الإيصالات المعالجة والمكررة والمتجاهلة والفاشلة؛ التفاصيل في «آخر الأحداث».

الطلب
{
  "purchases": [
    {
      "externalId": "R-2026-000200",
      "amount": 31.5,
      "currency": "TND",
      "type": "sale",
      "customer": {
        "phone": "+21698123456",
        "name": "Amira"
      },
      "items": [
        {
          "sku": "CROISSANT",
          "name": "Croissant",
          "quantity": 3,
          "unitPrice": 2.5
        }
      ]
    },
    {
      "externalId": "R-2026-000201",
      "amount": 12,
      "currency": "TND",
      "customer": {
        "email": "[email protected]"
      }
    },
    {
      "externalId": "R-2026-000202",
      "amount": 9,
      "type": "refund"
    }
  ]
}
200 OKاستجابة حقيقية من الواجهة (بيانات تجريبية)
{
  "received": 3,
  "processed": 1,
  "duplicates": 1,
  "ignored": 1,
  "failed": 0
}

توقيع اختياري

إذا عيّنت مفتاح توقيع على الربط، يجب أن يحمل كل طلب X-Dawwerni-Timestamp وX-Dawwerni-Signature (نفس طريقة Webhooks الصادرة، بهامش 5 دقائق).

Node.js
// Optional: sign the request when a signing secret is set on the connection
const crypto = require('crypto');
const body = JSON.stringify(sale);
const ts = Math.floor(Date.now() / 1000).toString();
const signature = 'sha256=' + crypto.createHmac('sha256', process.env.DAWWERNI_POS_SIGNING_SECRET)
  .update(ts + '.' + body).digest('hex');
await fetch(process.env.DAWWERNI_POS_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-Dawwerni-Timestamp': ts, 'X-Dawwerni-Signature': signature },
  body
});

أخطاء رابط الكاشير

400INVALID_JSONالمحتوى ليس JSON صالحاً
400PAYLOAD_TOO_LARGEالحد الأقصى للمحتوى 512 كيلوبايت
401INVALID_CONNECTION_TOKENربط غير معروف أو رمز غير صالح
401INVALID_SIGNATUREتوقيع ناقص أو قديم أو خاطئ
403CONNECTION_PAUSEDالربط متوقف في دورني
403FEATURE_NOT_ENABLEDالتكاملات غير مشمولة في الباقة
404UNKNOWN_CONNECTORنوع كاشير غير معروف في الرابط
422UNSUPPORTED_PAYLOADمحتوى غير مقروء
429RATE_LIMITED120 حدثاً في الدقيقة لكل ربط

الودجت والتضمين في الويب

اعرض ألعابك ونماذجك على موقعك أو متجرك الإلكتروني، دون API ودون مطوّر.

ودجت الألعاب (widget.js)

اختر لعبة ونمط العرض (زر عائم أو نافذة منبثقة أو داخل صفحة) والمواقع المسموح بها؛ التطبيق يعطيك الكود الجاهز للصق.

داخل التطبيقالقائمةالحملاتالموقع والمتجر الإلكترونيأداة جديدة

HTML
<!-- Floating button or pop-up: paste before </body> -->
<script src="https://app.dawwerni.com/widget.js" data-dawwerni-key="dw_YOUR_WIDGET_KEY" async></script>
  • اللعبة تفتح في إطار معزول (Shadow DOM + iframe): تنسيقات موقعك لا تؤثر عليها، والعكس صحيح.
  • نافذة منبثقة فوراً، أو بعد مهلة، أو عند التمرير، أو عند نية المغادرة، مع تحديد تكرار العرض.
  • المواقع المسموح بها: الودجت يظهر فقط على نطاقاتك (ونطاقاتها الفرعية).
  • لغة اللعبة: الخاصية data-lang (ar وfr وen)، وإلا لغة صفحتك.
  • مرات الظهور والفتح تُحتسب في التطبيق. يتم تفعيل الودجت عند الطلب: تواصل معنا.

نمط «داخل صفحة»

HTML
<!-- Inline: the game appears inside this block -->
<div data-dawwerni-game="dw_YOUR_WIDGET_KEY"></div>
<script src="https://app.dawwerni.com/widget.js" data-dawwerni-key="dw_YOUR_WIDGET_KEY" data-lang="ar" async></script>

نموذج تواصل مضمّن

يمكن تضمين نموذج دورني منشور داخل iframe؛ يرسل النموذج ارتفاعه ليتأقلم دون شريط تمرير.

داخل التطبيقالقائمةالعملاء المحتملوننماذج الطلباتمشاركةتضمين في موقعك

HTML
<iframe id="dw-lead-your-form" src="https://app.dawwerni.com/f/your-form?embed=1&src=website"
  title="Contact form" style="width:100%;max-width:600px;min-height:640px;border:0" loading="lazy"></iframe>
<script>
  // optional: the form reports its height so the frame never shows a scrollbar
  window.addEventListener('message', function (e) {
    if (e.origin !== 'https://app.dawwerni.com') return;
    var d = e.data || {};
    if (d.type === 'dawwerni-lead-form:height' && d.slug === 'your-form') {
      document.getElementById('dw-lead-your-form').style.height = (d.height + 8) + 'px';
    }
  });
</script>

تحتاج مساعدة في الربط؟

كاشير غير مذكور، نظام ERP، أو تكامل حسب الطلب (باقة المؤسسات)؟ أخبرنا بالأداة التي تستخدمها ونرد عليك بأفضل حل.