إن لم يكن متجرك الإلكتروني على WooCommerce أو Shopify أو سلة أو زد، فما زال يستطيع إرسال طلباته وطلبات عروض الأسعار ومنتجاته إلى مساحة عملك. يرسل متجرك مستند JSON صغيرًا موقعًا كلما تغير شيء، فيصبح كل طلب جهة اتصال وطلب متجر، ويصبح مبيعًا في تقاريرك حين يُدفع. هذه الصفحة هي العقد الذي يعمل به مطور ذلك المتجر. أما WooCommerce فاتبع له WooCommerce.
قبل أن تبدأ
- ربط المتاجر متاح عندما تشمل باقتك ربط المتاجر. وقد تحدد باقتك أيضًا عدد الروابط النشطة.
- المالكون والمسؤولون وحدهم ينشئون الربط ويوقفونه ويستبدلون سره.
- تحتاج مسارًا واحدًا على الأقل، لأن طلبات عروض الأسعار تفتح فيه فرصًا.
- يجب أن يستطيع متجرك إرسال طلب
POSTعبر HTTPS من خادمه، ويُفضَّل من مهمة في الخلفية.
إنشاء الربط
- افتح المنتجات من الشريط الجانبي، ثم ربط المتاجر.
- تحت ربط متجر اكتب اسم المتجر.
- اجعل منصة المتجر على منصتنا أو متجر مخصص.
- اختر المرحلة في تبدأ طلبات عروض الأسعار في، واختر إن شئت شخصًا في إسناد الفرص الجديدة إلى.
- اترك سر التوقيع من Shopify أو سلة فارغًا.
- اختر ربط متجر.
- انسخ سر التوقيع، فهو يظهر مرة واحدة فقط.
- انسخ العنوان الذي يرسل إليه متجرك من بطاقة الربط.
احفظ القيمتين في إعدادات بيئة متجرك، لا في الكود:
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.');
}
}
}
تجربة الربط
- أرسل
order.createdلعميل تجريبي بحالةpending. - تأكد أن بطاقة الربط تُظهر آخر حدث مع الوقت، وأن العميل ظهر في جهات الاتصال مع طلبه.
- أرسل
order.updatedمع"status": "paid"وupdated_atأحدث. يجب أن يُحسب الطلب الآن مبيعًا. - أرسل
order.updatedمع"status": "cancelled"وupdated_atأحدث. يجب أن يخرج الطلب من أرقام المبيعات.
معلومات مفيدة
- لا تُستورد الطلبات السابقة للمتاجر المخصصة، فلا يصل إلا ما ترسله من الآن.
- استبدال السر ينشئ سرًا جديدًا فورًا. والأحداث الموقعة بالسر القديم تُرفض بـ
403منذ تلك اللحظة، فحدّث متجرك فورًا وأعد إرسال ما رُفض في الأثناء. - إيقاف يوقف الربط، فترد الأحداث
404حتى يُعاد تشغيل الربط. - العميل الممحو يبقى ممحوًا. والأحداث اللاحقة لطلباته تُبقي المجاميع صحيحة ولا تعيد أي بيانات شخصية.
حل المشكلات
| المشكلة | ما تتحقق منه |
|---|---|
403 دائمًا |
السر، والساعة، وأنك توقّع الجسم نفسه الذي ترسله، يسبقه الوقت ونقطة |
422 مع total |
أُرسل المجموع رقمًا، أو استُخدمت الفاصلة علامة عشرية |
422 مع customer |
الطلب ليس فيه بريد صالح ولا هاتف من 6 إلى 15 رقمًا |
422 مع currency |
العملة ليست من العملات الست المدعومة |
| تظهر أحداث مرفوضة على البطاقة | وصلت أحداث حين لم تكن الباقة تشمل ربط المتاجر. لم تُحفظ، فأعد إرسالها |
| لم يغيّر التحديث الطلب | لم يكن updated_at فيه أحدث من المحفوظ |
آخر تحديث 2026-11-08