الدليل
الأتمتة

إرسال بيانات العميل بـ Webhook

ابعت بيانات العملاء من أتمتة ردلي للـCRM أو نظامك عبر Webhook: ظبط الوجهة، واتأكد من توقيع كل طلب، وتعامل مع إعادة المحاولة من غير ما تسجّل العميل مرتين.

الدليل ده للمطور اللي هيربط endpoint خاص بالمتجر (CRM أو سكريبت أو أي نظام بيستقبل طلبات HTTPS) علشان يستلم بيانات العملاء من أتمتة ردلي.

بيشتغل إزاي

  1. من أتمتة إرسال بيانات العميل، ضيف وجهة باسم ورابط HTTPS. ردلي بيعمل secret للتوقيع وبيظهره مرة واحدة بس وقت الحفظ.
  2. اختار أول رسالة من العميل للإرسال مرة في المحادثة، أو كل رسالة من العميل علشان يوصلك إشعار مع كل رسالة واردة.
  3. تقدر تبعت event تجريبي من نوع webhook.test.v1 وتتابع آخر عمليات الإرسال من نفس خطوة الوجهة.

كل وجهة ليها رابط واحد. مع أي شرط لإرسال بيانات العميل، أول إشعار للعميل على الوجهة بيكون lead.created.v1، وكل إشعار بعدها بيكون lead.updated.v1 بنفس data.leadId. التحديث معناه إن العميل راسلك تاني، مش إن بياناته الشخصية اتغيّرت.

الإشعارات بتشتغل حتى لو الفريق ماسك المحادثة أو ردود الذكاء الاصطناعي واقفة. مش بتغيّر مين بيرد، ومش بتشمل ردود الفريق أو الذكاء الاصطناعي، ومش بتبعت محتوى الرسايل أو المرفقات. كل رسالة ليها إشعار مستقل حتى لو العميل بعت كذا رسالة بسرعة.

اختيار كل رسالة من العميل متاح في أتمتة إرسال بيانات العميل بس. لو عندك أتمتة أول رسالة لنفس الوجهة وعايز إشعارات مستمرة، غيّر شرطها بدل ما تسيب الاتنين شغالين. تفعيل الأتمتة مش بيعيد إرسال الرسايل القديمة؛ الإشعارات بتبدأ مع الرسايل الجديدة وهي شغالة. إيقافها بيوقف الإشعارات الجديدة، لكن الإشعارات اللي اتحفظت قبل الإيقاف بتكمل محاولات إرسالها.

أتمتة أول رسالة وشروط مطابقة الرسالة بتستخدم نفس القاعدة: لو الوجهة تعرف العميل، الإشعار بيكون lead.updated.v1 بنفس data.leadId. لو أكتر من أتمتة اشتغلت لنفس العميل، ممكن يوصلك أكتر من إشعار، لكن إنشاء العميل بيحصل مرة واحدة. الإشعارات القديمة المحفوظة بتفضل بنفس نوعها. webhook.test.v1 بيتبعت من زرار اختبر الوجهة بس ومفيهوش بيانات عميل.

ظبط الوجهة من لوحة التحكم

افتح الأتمتة واعمل أتمتة جديدة، وبعدها:

  1. اختار ابعت بيانات العميل.
  2. سمّي الأتمتة وحدد إمتى تشتغل.
  3. في خطوة الوجهة، اكتب اسم النظام ورابط الـHTTPS.
  4. احفظ الـsecret أول ما يظهر؛ مش هتقدر تعرضه تاني.
  5. دوس اختبر الوجهة واتأكد إن النتيجة ناجحة قبل ما تفعّل الأتمتة.

لو فقدت الـsecret أو غيّرت الرابط، امسح الوجهة واعمل واحدة جديدة. مفيش تعديل لرابط موجود أو تدوير منفصل للـsecret حاليًا.

الطلب اللي ردلي بيبعته

POST <destination URL>
content-type: application/json
x-roddely-event: lead.created.v1
x-roddely-event-id: <event id, the same value on every retry>
x-roddely-timestamp: <unix seconds>
x-roddely-signature: v1=<hex HMAC-SHA256 of `${timestamp}.${body}` under your destination secret>

مثال توضيحي لعميل واتساب، بأرقام ومعرّفات للتوضيح. أول رسالة مع شرط كل رسالة من العميل:

{
  "id": "evt_001",
  "type": "lead.created.v1",
  "occurredAt": "2026-09-08T10:00:00.000Z",
  "data": {
    "leadId": "lead_abc123",
    "conversationId": "conv_456",
    "automationId": "auto_789",
    "customer": {
      "channel": "whatsapp",
      "externalId": "201012345678",
      "externalIdKind": "phone_number",
      "phone": "201012345678",
      "displayName": "Ahmed Hassan"
    }
  }
}

نفس العميل بعت رسالة تانية بعد خمس دقايق. الـheader x-roddely-event هنا بيكون lead.updated.v1 وx-roddely-event-id بيكون evt_002:

{
  "id": "evt_002",
  "type": "lead.updated.v1",
  "occurredAt": "2026-09-08T10:05:00.000Z",
  "data": {
    "leadId": "lead_abc123",
    "conversationId": "conv_456",
    "automationId": "auto_789",
    "customer": {
      "channel": "whatsapp",
      "externalId": "201012345678",
      "externalIdKind": "phone_number",
      "phone": "201012345678",
      "displayName": "Ahmed Hassan"
    }
  }
}

معنى الحقول:

  • data.leadId هو معرّف العميل الثابت عند الوجهة دي؛ استخدمه علشان تحدّث نفس الـlead. العميل المعروف بنفس معرّف القناة في محادثة جديدة يفضل تحديثًا، والوجهة الجديدة تستقبل إنشاء أول مرة. مفيش دمج تلقائي بين عميل واتساب وعميل على قناة تانية.

  • occurredAt وقت رسالة العميل في إشعارات كل رسالة، مش وقت محاولة توصيل الـwebhook.

  • id معرّف الإشعار، مختلف لكل رسالة وثابت لو نفس الإشعار اتبعت تاني. متستخدموش كمعرّف للعميل.

  • الطلبات ممكن توصل بترتيب مختلف بسبب إعادة المحاولة؛ اعمل upsert باستخدام leadId حتى لو التحديث وصل قبل الإنشاء، وحدّث «آخر تواصل» بأحدث occurredAt فقط.

  • إشعارات قديمة اتحفظت قبل إضافة التحديثات ممكن توصل من غير leadId، بنفس الـbody القديم. العملاء اللي سبق إرسالهم بيتحسبوا معروفين عند تفعيل التحديثات؛ أول event تاريخي ليهم بيكون معرّف الـlead. غياب إشعارات تاريخية محفوظة معناه إن أول إشعار جديد هو بداية التتبع.

  • channel واحد من whatsapp أو instagram أو messenger أو telegram أو web.

  • externalId موجود دايمًا، لكنه تابع للقناة ومش رقم تليفون إلا لو externalIdKind قال كده.

  • externalIdKind بيتبعت مع واتساب بس، وقيمته بتكون phone_number أو business_scoped_user_id أو linked_device_id. القيمة الوحيدة اللي ينفع تتصل بيها كرقم هي phone_number.

  • phone وdisplayName مش بيتبعتوا أصلًا لو ردلي معندوش قيمة حقيقية؛ مش بيتبعتوا null أو نص فاضي. متخليش غياب الحقل يمسح قيمة سليمة في الـCRM.

  • مع channel: "web"، قيمة externalId فاضية عمدًا لأن محادثة الـwidget ملهاش هوية provider. ردلي بيستخدم المحادثة كحدود الهوية في الحالة دي، فزيارة جديدة في محادثة جديدة بتكون lead جديد.

  • محتوى الرسايل مش بيتبعت، وردلي مش بيخمّن رقم أو اسم أو رابط profile.

  • type عليه version. إضافة حقل جديد ممكن تحصل داخل lead.created.v1، لكن أي تغيير كاسر هياخد event جديد زي lead.created.v2.

Event الاختبار webhook.test.v1 بيستخدم نفس الـheaders ونفس التوقيع، والـbody فيه { id, type, occurredAt } بس من غير data.

اتأكد من التوقيع قبل ما تستخدم البيانات

  1. اقرا x-roddely-timestamp كـUnix seconds، وارفض الطلب لو الفرق بينه وبين ساعة السيرفر أكتر من ٥ دقايق.
  2. احسب HMAC-SHA256("${timestamp}.${rawBody}") بالـsecret بتاع الوجهة، وقارن النتيجة في constant time بالـhex اللي بعد v1= في x-roddely-signature.
  3. استخدم بايتات الـraw request body، مش JSON اتعمله parse واتحوّل لنص تاني؛ ترتيب المفاتيح أو المسافات ممكن يتغيروا ويبوّظوا التوقيع.
  4. استخدم x-roddely-event-id كمفتاح dedup ثابت. خزّنه في storage دائم بقيد unique قبل أي side effect، ولو شفته تاني اعتبره نفس الـevent. Set في الذاكرة مش كفاية لأنه بيضيع لما السيرفر يعمل restart.
  5. ارجع 2xx أول ما تحفظ الـevent بشكل دائم، وبعدها اعمل الشغل البطيء. ردلي مش بيقرا response body وبيستنى ١٠ ثواني بس؛ لو اتأخرت، هيقفل المحاولة ويعيدها بنفس event id.

مثال Node.js:

import crypto from "node:crypto"

function isValidRoddelySignature(headers, rawBody, secret) {
  const timestamp = Number(headers["x-roddely-timestamp"])
  if (!Number.isFinite(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) {
    return false
  }
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex")
  const provided = String(headers["x-roddely-signature"]).replace(/^v1=/, "")
  return (
    expected.length === provided.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(provided))
  )
}

إعادة المحاولة وحالات الفشل

إعادة المحاولة بتبدأ بعد حفظ الإشعار في طابور الإرسال. لو حصل خطأ داخلي أثناء تسجيل إشعار «كل رسالة من العميل»، رسالة العميل بتفضل محفوظة، لكن الإشعار ده بيتخطّى من غير إعادة تسجيل تلقائية.

اللي حصلالنتيجةردلي هيعمل إيه
الرد 2xxdeliveredالنجاح الوحيد؛ response body مش بيتقري
429 أو 5xx أو timeout أو فشل الاتصالretrying ثم blockedتأخير تصاعدي: ٣٠ث، ١د، ٢د، ٤د، ٨د، ١٦د، ٣٢د خلال ٨ محاولات، بحد أقصى ساعة
فشل DNSretrying ثم blockedفشل مؤقت وبيستخدم نفس التأخير
أي 4xx تانيblocked فورًامفيش إعادة تلقائية لأن الانتظار مش هيصلّح رفض دائم
redirect من نوع 3xxblocked فورًاردلي مش بيتبع redirects
الرابط اتحل لعنوان محظورblocked فورًاالطلب مش بيتبعت

عملية blocked مش بتترمي. بعد ما تصلّح الـendpoint، افتح سجل عمليات الإرسال من خطوة الوجهة في لوحة التحكم واضغط إعادة الإرسال علشان ترجعها للطابور بعدّاد محاولات جديد.

الأمان

  • HTTPS إجباري، والرابط بيتفحص وقت الحفظ وقبل كل محاولة.
  • ردلي بيعمل DNS resolution من جديد قبل كل إرسال، وبيمنع العناوين الخاصة والداخلية وعناوين خدمات metadata. لو domain كان public وبعدها بدأ يشاور على عنوان داخلي، المحاولة الجديدة هتترفض.
  • الاتصال بيتثبت على العناوين اللي اتفحصت علشان تغيير نتيجة DNS بين الفحص والاتصال مينفعش يعدّي الحماية.
  • أي redirect بيترفض بدل ما ردلي يتبعه.
  • رابط الوجهة والـhost والـsecret مش بيدخلوا logs أو error messages عند ردلي.

Checklist للـreceiver

  • اقرا الـraw body قبل JSON parsing.
  • ارفض timestamp أقدم أو أحدث من ٥ دقايق.
  • اتأكد من توقيع HMAC في constant time.
  • خزّن event id بقيد unique قبل أي side effect.
  • ارجع 2xx بعد الحفظ الدائم بسرعة، وكمل المعالجة بعده.
  • متفترضش إن externalId رقم تليفون.
  • تعامل مع الحقول الاختيارية الغايبة كـ«معلومة مش موجودة»، مش كأمر لمسح بيانات قديمة.

بعد ما الـreceiver ينجح في الاختبار، ارجع لصفحة الأتمتة وفعّل التعريف اللي بيستخدم الوجهة.