رُتبة CRM

أحداث المتجر المخصص

أرسل أحداث الطلبات وعروض الأسعار والمنتجات موقعة من متجرك الإلكتروني الخاص إلى رُتبة CRM، بصيغة JSON والتوقيع الدقيقين.

إن لم يكن متجرك الإلكتروني على WooCommerce أو Shopify أو سلة أو زد، فما زال يستطيع إرسال طلباته وطلبات عروض الأسعار ومنتجاته إلى مساحة عملك. يرسل متجرك مستند JSON صغيرًا موقعًا كلما تغير شيء، فيصبح كل طلب جهة اتصال وطلب متجر، ويصبح مبيعًا في تقاريرك حين يُدفع. هذه الصفحة هي العقد الذي يعمل به مطور ذلك المتجر. أما WooCommerce فاتبع له WooCommerce.

قبل أن تبدأ

  • ربط المتاجر متاح عندما تشمل باقتك ربط المتاجر. وقد تحدد باقتك أيضًا عدد الروابط النشطة.
  • المالكون والمسؤولون وحدهم ينشئون الربط ويوقفونه ويستبدلون سره.
  • تحتاج مسارًا واحدًا على الأقل، لأن طلبات عروض الأسعار تفتح فيه فرصًا.
  • يجب أن يستطيع متجرك إرسال طلب POST عبر HTTPS من خادمه، ويُفضَّل من مهمة في الخلفية.

إنشاء الربط

  1. افتح المنتجات من الشريط الجانبي، ثم ربط المتاجر.
  2. تحت ربط متجر اكتب اسم المتجر.
  3. اجعل منصة المتجر على منصتنا أو متجر مخصص.
  4. اختر المرحلة في تبدأ طلبات عروض الأسعار في، واختر إن شئت شخصًا في إسناد الفرص الجديدة إلى.
  5. اترك سر التوقيع من Shopify أو سلة فارغًا.
  6. اختر ربط متجر.
  7. انسخ سر التوقيع، فهو يظهر مرة واحدة فقط.
  8. انسخ العنوان الذي يرسل إليه متجرك من بطاقة الربط.

احفظ القيمتين في إعدادات بيئة متجرك، لا في الكود:

STORE_EVENTS_URL=STORE_ADDRESS
STORE_EVENTS_SECRET=YOUR_STORE_SECRET

الطلب

أرسل كل حدث طلب POST إلى عنوان الربط، مستند JSON في الجسم، ومعه هذه الترويسات:

الترويسة القيمة
X-CRM-Timestamp وقت Unix الحالي بالثواني
X-CRM-Signature sha256= يليها HMAC-SHA256 بالصيغة الست عشرية بأحرف صغيرة، للوقت ثم نقطة ثم الجسم كما هو، بسر التوقيع مفتاحًا
Content-Type application/json

يجب أن يكون الوقت ضمن 5 دقائق من ساعة الخادم، فأبقِ ساعة متجرك مضبوطة. ويُفحص التوقيع على البايتات الخام التي ترسلها، فوقّع السلسلة نفسها التي تضعها في الجسم.

BODY='{"event":"order.created","order":{"id":"1001","number":"1001","status":"pending","currency":"SAR","total":"1250.50","placed_at":"2026-11-06T10:00:00Z","customer":{"name":"Ali Hassan","email":"ali@example.com"}}}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "YOUR_STORE_SECRET" | sed 's/^.* //')

curl -X POST "STORE_ADDRESS" \
  -H "Content-Type: application/json" \
  -H "X-CRM-Timestamp: $TS" \
  -H "X-CRM-Signature: sha256=$SIG" \
  --data "$BODY"

الأحداث

event مفتاح المستند الأثر
order.created order يحفظ الطلب ويجد عميله أو ينشئه
order.updated order مثل order.created. أرسله كلما تغيرت الحالة أو المجموع
quote.requested quote يفتح فرصة بالبنود المطلوبة في المرحلة المختارة، مرة واحدة لكل معرف عرض
product.upserted product ينشئ منتجًا في الكتالوج أو يحدّثه

وأي قيمة أخرى في event تُرفض بـ422.

مستند الطلب

{
  "event": "order.updated",
  "order": {
    "id": "1001",
    "number": "SO-1001",
    "status": "paid",
    "currency": "SAR",
    "total": "1250.50",
    "placed_at": "2026-11-06T10:00:00Z",
    "updated_at": "2026-11-06T10:05:00Z",
    "customer": {
      "name": "Ali Hassan",
      "email": "ali@example.com",
      "phone": "+966500000002",
      "company": "Hassan Cafe"
    },
    "items": [
      {"sku": "ESP-1", "name": "Espresso machine", "quantity": 1, "total": "1250.50"}
    ],
    "attribution": {
      "source": "google",
      "medium": "cpc",
      "campaign": "ramadan-sale"
    }
  }
}
الحقل مطلوب القواعد
id نعم معرف الطلب في متجرك، حتى 100 حرف، ويحدد الطلب في كل حدث لاحق
number لا رقم الطلب الذي يراه العملاء، حتى 60 حرفًا
status لا حتى 40 حرفًا، ويُحفظ بأحرف صغيرة. الافتراضي pending. راجع الحالات أدناه
currency نعم USD أو EUR أو GBP أو TRY أو SAR أو AED
total نعم نص مثل "1250.50". والرقم في JSON يُرفض
placed_at نعم وقت إنشاء الطلب، تاريخًا ووقتًا بصيغة ISO 8601
updated_at لا وقت آخر تغيير في الطلب، والافتراضي placed_at
customer.name نعم حتى 160 حرفًا
customer.email أحدهما عنوان صالح حتى 254 حرفًا
customer.phone أحدهما حتى 40 حرفًا. والهاتف الذي لا يكون من 6 إلى 15 رقمًا بعد التنظيف يُسقط
customer.company لا حتى 160 حرفًا
items لا حتى 200 بند، لكل منها name (مطلوب، حتى 200 حرف) وquantity (عدد صحيح من 1 إلى 1000000) وtotal (نص) وsku اختياري
attribution لا source وmedium وcampaign، كل منها حتى 120 حرفًا. وتظهر حملةً للطلب في التقارير
recovery_url لا رابط https حتى 2000 حرف يستطيع العميل منه إكمال الدفع. تستخدمه تذكيرات الطلبات غير المكتملة
consent لا كائن حتى 50 حقلًا من حقول الطلب. وحين يُضبط حقل الطلب الذي يحفظ موافقة واتساب في التنبيهات والأتمتة، فإن قيمة الموافقة في ذلك الحقل، في طلب حالته pending أو failed ومعه recovery_url، تسجل موافقة العميل على واتساب

التقط وسوم الحملة (utm_source وutm_medium وutm_campaign) عند دخول الزائر واحفظها مع الطلب، لتحملها attribution.

الحالات

الحالة تُعامل على أنها
paid وprocessing وcompleted وdelivered مبيع. تُحسب في تقارير المبيعات، وتحوّل العميل المحتمل إلى عميل، وتطلق تأكيد طلب متجر في أول مرة
pending وfailed لم يُدفع بعد. ومع recovery_url قد تبدأ تذكيرات الطلبات غير المكتملة إن كانت مفعلة
أي حالة أخرى، مثل cancelled أو refunded ليس مبيعًا. ولا يوقف تذكير هذا الطلب إلا إذا حمل الحدث recovery_url الخاص به، فاستمر في إرساله

كيف يُطبَّق الطلب

  • الأحدث يغلب. الحدث الذي يكون updated_at فيه أقدم من المحفوظ يُتجاهل. والذي له updated_at نفسه والحالة نفسها والمجموع نفسه يُتجاهل تكرارًا. فأرسل updated_at جديدًا مع كل تغيير.
  • يُطابَق العميل ولا يُكتب فوقه. البريد يطابق أولًا جهة نشطة موجودة. والهاتف لا يطابق إلا جهة ليس لها بريد آخر، فلا يجمع هاتف مشترك شخصين. ومن دون مطابقة تُنشأ جهة جديدة عميلًا محتملًا مصدره الموقع، مسندة إلى الشخص المختار في إسناد الفرص الجديدة إلى، ويطلق ذلك وصول عميل محتمل جديد.
  • يثبت العميل من الحدث الأول. الأحداث اللاحقة لمعرف الطلب نفسه تبقى على الجهة نفسها.
  • كل حدث آمن للتكرار. إعادة إرسال المستند نفسه لا تغير شيئًا.

مستند عرض السعر

يستخدم quote.requested حقول الطلب نفسها تحت المفتاح quote. وينشئ فرصة واحدة عنوانها رقم العرض واسم العميل، في المرحلة المختارة للربط، وفيها بند لكل عنصر. ومبلغ الفرصة مجموع العناصر، أو total إن لم توجد عناصر. والحدث الثاني بمعرف العرض نفسه يُتجاهل.

مستند المنتج

{
  "event": "product.upserted",
  "product": {
    "id": "P-77",
    "name": "Espresso machine",
    "sku": "ESP-1",
    "price": "1250.50",
    "currency": "SAR",
    "active": true,
    "updated_at": "2026-11-06T09:00:00Z"
  }
}
الحقل مطلوب القواعد
id نعم معرف المنتج لديك، حتى 100 حرف
name نعم حتى 200 حرف
sku لا حتى 100 حرف. ويُرفض SKU يستخدمه منتج آخر
price لا نص، ولا يُحفظ إلا مع currency
currency لا إحدى العملات المدعومة
active لا false تؤرشف المنتج
updated_at لا الحدث الأقدم من المحفوظ يُتجاهل

يُعثر على المنتج بـid، وإلا بـsku، فالمنتج المكتوب يدويًا من قبل يُربط ولا يُكرر.

الردود

الحالة الجسم المعنى
200 {"status": "received"} حُفظ، أو عُرف أنه تكرار
200 {"status": "paused"} التوقيع صحيح، لكن الباقة لا تشمل ربط المتاجر أو الشركة موقوفة. لا يُحفظ شيء، وتعد الشاشة الحدث مرفوضًا
403 توقيع خاطئ، أو وقت يبتعد أكثر من 5 دقائق
404 الربط غير موجود أو موقوف. وفي quote.requested أيضًا حين تُؤرشف مرحلة الربط أو مسارها
422 {"errors": ["customer.email", "total"]} المستند غير صالح، والقائمة تسمي الحقول المخطئة. والجسم الذي ليس كائن JSON يتلقى 422 دون قائمة
429 أكثر من 600 حدث في الدقيقة من عنوان واحد

الإرسال من متجرك

أرسل من مهمة في الطابور بعد حفظ الطلب، فلا يؤخر ربط بطيء أو متوقف عملية الشراء. وأعد المحاولة عند 5xx و429 وأخطاء الاتصال مع مهلة تطول. ولا تُعد 403 قبل إصلاح الساعة أو السر، ولا تُعد إرسال 422 دون تغيير.

final class StoreEvents
{
    public function send(string $event, string $key, array $document): void
    {
        $body = json_encode(['event' => $event, $key => $document], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
        $time = (string) time();
        $signature = 'sha256=' . hash_hmac('sha256', $time . '.' . $body, getenv('STORE_EVENTS_SECRET'));

        $curl = curl_init(getenv('STORE_EVENTS_URL'));
        curl_setopt_array($curl, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $body,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 5,
            CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-CRM-Timestamp: ' . $time, 'X-CRM-Signature: ' . $signature],
        ]);
        curl_exec($curl);
        $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
        curl_close($curl);

        if ($status === 0 || $status === 429 || $status >= 500) {
            throw new RuntimeException('Store event not delivered, retry later.');
        }
    }
}

تجربة الربط

  1. أرسل order.created لعميل تجريبي بحالة pending.
  2. تأكد أن بطاقة الربط تُظهر آخر حدث مع الوقت، وأن العميل ظهر في جهات الاتصال مع طلبه.
  3. أرسل order.updated مع "status": "paid" وupdated_at أحدث. يجب أن يُحسب الطلب الآن مبيعًا.
  4. أرسل order.updated مع "status": "cancelled" وupdated_at أحدث. يجب أن يخرج الطلب من أرقام المبيعات.

معلومات مفيدة

  • لا تُستورد الطلبات السابقة للمتاجر المخصصة، فلا يصل إلا ما ترسله من الآن.
  • استبدال السر ينشئ سرًا جديدًا فورًا. والأحداث الموقعة بالسر القديم تُرفض بـ403 منذ تلك اللحظة، فحدّث متجرك فورًا وأعد إرسال ما رُفض في الأثناء.
  • إيقاف يوقف الربط، فترد الأحداث 404 حتى يُعاد تشغيل الربط.
  • العميل الممحو يبقى ممحوًا. والأحداث اللاحقة لطلباته تُبقي المجاميع صحيحة ولا تعيد أي بيانات شخصية.

حل المشكلات

المشكلة ما تتحقق منه
403 دائمًا السر، والساعة، وأنك توقّع الجسم نفسه الذي ترسله، يسبقه الوقت ونقطة
422 مع total أُرسل المجموع رقمًا، أو استُخدمت الفاصلة علامة عشرية
422 مع customer الطلب ليس فيه بريد صالح ولا هاتف من 6 إلى 15 رقمًا
422 مع currency العملة ليست من العملات الست المدعومة
تظهر أحداث مرفوضة على البطاقة وصلت أحداث حين لم تكن الباقة تشمل ربط المتاجر. لم تُحفظ، فأعد إرسالها
لم يغيّر التحديث الطلب لم يكن updated_at فيه أحدث من المحفوظ

آخر تحديث 2026-11-08