المحتويات
كيف تعمل الويبهوكس
يرسل روّج طلب POST عبر HTTPS إلى رابطك عند وقوع حدث في مساحة عملك: جدولة منشور أو نشره أو فشله أو إلغاؤه، أو ربط حساب أو انقطاعه أو قرب انتهائه، أو تغيّر في فريقك، أو اقتراب من حد استخدام، أو جاهزية تحليلات اليوم.
- الويبهوكس جزء من خطة المؤسسات.
- حتى 5 روابط لكل مساحة عمل، ويشترك كل رابط في الأحداث التي يختارها.
- تصل الأحداث خلال 5 دقائق، وكل طلب موقّع.
- كل ما يحدث في مساحة عملك يُحتسب، أياً كان عضو الفريق الذي قام به.
إعداد رابط
- افتح الإعدادات ثم الويبهوكس.
- أضف رابط نقطة النهاية. يجب أن يكون HTTPS ومتاحاً للعامة.
- اختر الأحداث. يمكنك تغييرها لاحقاً دون تغيير المفتاح السري.
- انسخ المفتاح السري عند ظهوره. يظهر مرة واحدة ولا يمكن استرجاعه.
- اضغط «إرسال تجريبي». يصلك مثال لأحد أحداثك خلال 5 دقائق ويحمل
"test": true.
شكل كل طلب
طلب POST بمحتوى JSON وهذه الترويسات:
X-Rawwij-Eventنوع الحدث، مثلpost.publishedX-Rawwij-Deliveryمعرّف التسليم، وهو نفس قيمة id في المحتوىX-Rawwij-Timestampوقت إرسال هذه المحاولة بالثواني (Unix)X-Rawwij-Signaturev1=متبوعاً بالتوقيع
المحتوى
{
"id": "0b6f3c1e-8a2d-4f7b-9c5e-1d4a7b2c9e80",
"type": "post.published",
"created_at": "2026-10-01T09:01:12.000Z",
"test": false,
"data": {
"post_id": "6f1c2a8e-4b1d-4c55-9a0e-2d7b3f9c1a42",
"status": "published",
"scheduled_at": "2026-10-01T09:00:00.000Z",
"published_at": "2026-10-01T09:01:12.000Z",
"error": null,
"failed_count": 0,
"accounts": [
{
"account_id": "b3e9d1f0-7a2c-4e8b-9c4d-1f6a5e2b8c73",
"platform": "instagram",
"username": "rawwij",
"status": "published",
"url": "https://www.instagram.com/p/DAbCdEfGhIj/",
"platform_post_id": "18034567890123456",
"error": null,
"text": "Our autumn collection is here. Visit the store this weekend.",
"media_type": "image",
"media_count": 1
}
]
}
}idيبقى كما هو عند إعادة المحاولة أو إعادة الإرسال. استخدمه لتجاهل التكرار.created_atهو وقت وقوع الحدث، لا وقت إرسال هذه المحاولة.testيكون true فقط للتسليمات المرسلة بزر الإرسال التجريبي.platformقيمته واحدة من: instagram وfacebook وthreads وtiktok وtwitter (X) وlinkedin وyoutube وtelegram.
التحقق من التوقيع
احسب HMAC-SHA256 للنص {timestamp}.{raw body} بمفتاح رابطك السري وبصيغة hex. يجب أن يطابق القيمة التي تلي v1= في X-Rawwij-Signature. قارن بزمن ثابت، وارفض أي طابع زمني أقدم من 5 دقائق.
Node.js
import crypto from 'node:crypto';
// rawBody: the request body exactly as received, before any JSON parsing.
export function verifyRawwij(rawBody, headers, secret) {
const timestamp = String(headers['x-rawwij-timestamp'] ?? '');
const signature = String(headers['x-rawwij-signature'] ?? '').replace(/^v1=/, '');
// Refuse anything older than five minutes, so a captured request cannot be replayed.
if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return false;
}
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`, 'utf8')
.digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(signature, 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Python
import hashlib
import hmac
import time
# raw_body: the request body exactly as received, as bytes.
def verify_rawwij(raw_body: bytes, headers, secret: str) -> bool:
timestamp = headers.get("X-Rawwij-Timestamp", "")
signature = headers.get("X-Rawwij-Signature", "").removeprefix("v1=")
# Refuse anything older than five minutes, so a captured request cannot be replayed.
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return False
expected = hmac.new(
secret.encode("utf-8"),
timestamp.encode("utf-8") + b"." + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)إعادة المحاولة والفشل وإعادة الإرسال
- أجب بأي حالة 2xx خلال 10 ثوانٍ. أي رد آخر يُعد فشلاً، ولا تُتبع إعادات التوجيه.
- يُعاد إرسال التسليم الفاشل 5 مرات، بعد نحو دقيقة ثم 5 دقائق ثم 30 دقيقة ثم ساعتين ثم 6 ساعات، ثم يتوقف.
- بعد 20 فشلاً متتالياً يتوقف الرابط، ويصل مالك مساحة العمل إشعار وبريد إلكتروني. أعد تشغيله من الإعدادات، الويبهوكس.
- تظهر كل محاولة في سجل التسليمات مع رمز الاستجابة لمدة 30 يوماً. إعادة الإرسال ترسل البيانات الأصلية بنفس المعرّف.
- إذا خرجت مساحة العمل من خطة المؤسسات تتوقف روابطها عن استقبال الأحداث، وتبقى ظاهرة ليمكن حذفها.
جميع الأحداث
المنشورات
post.scheduledجُدول المنشورعند جدولة منشور من صفحة الإنشاء أو الجدولة المجمعة أو تطبيق iOS. حدث واحد لكل عملية جدولة مهما كان عدد الحسابات.
data
{
"post_id": "6f1c2a8e-4b1d-4c55-9a0e-2d7b3f9c1a42",
"scheduled_at": "2026-10-01T09:00:00.000Z",
"scheduled_by": "2a7e4c1b-9f3d-4b6a-8c2e-5d1f0a9b7c34",
"accounts": [
{
"account_id": "b3e9d1f0-7a2c-4e8b-9c4d-1f6a5e2b8c73",
"platform": "instagram",
"username": "rawwij",
"text": "Our autumn collection is here. Visit the store this weekend.",
"media_type": "image",
"media_count": 1
},
{
"account_id": "c8a1f4e2-3d5b-4a9c-8e7f-6b2d1c9a0e51",
"platform": "linkedin",
"username": "Rawwij",
"text": "Our autumn collection is here. Visit the store this weekend.",
"media_type": "image",
"media_count": 1
}
]
}post.publishedنُشر المنشورعند انتهاء جميع حسابات المنشور ونجاح حساب واحد على الأقل. يحمل رابط المنشور ومعرّفه لكل حساب، وسبب الفشل لأي حساب لم يُنشر عليه.
data
{
"post_id": "6f1c2a8e-4b1d-4c55-9a0e-2d7b3f9c1a42",
"status": "published",
"scheduled_at": "2026-10-01T09:00:00.000Z",
"published_at": "2026-10-01T09:01:12.000Z",
"error": null,
"failed_count": 0,
"accounts": [
{
"account_id": "b3e9d1f0-7a2c-4e8b-9c4d-1f6a5e2b8c73",
"platform": "instagram",
"username": "rawwij",
"status": "published",
"url": "https://www.instagram.com/p/DAbCdEfGhIj/",
"platform_post_id": "18034567890123456",
"error": null,
"text": "Our autumn collection is here. Visit the store this weekend.",
"media_type": "image",
"media_count": 1
}
]
}post.failedفشل المنشورعند انتهاء جميع حسابات المنشور دون نجاح أي منها. يحمل سبب الفشل لكل حساب.
data
{
"post_id": "6f1c2a8e-4b1d-4c55-9a0e-2d7b3f9c1a42",
"status": "failed",
"scheduled_at": "2026-10-01T09:00:00.000Z",
"published_at": null,
"error": "Facebook: You have been temporarily blocked from performing this action.",
"failed_count": 1,
"accounts": [
{
"account_id": "b3e9d1f0-7a2c-4e8b-9c4d-1f6a5e2b8c73",
"platform": "facebook",
"username": "Rawwij",
"status": "failed",
"url": null,
"platform_post_id": null,
"error": "You have been temporarily blocked from performing this action.",
"text": "Our autumn collection is here. Visit the store this weekend.",
"media_type": "image",
"media_count": 1
}
]
}post.cancelledأُلغي المنشورعند إلغاء منشور مجدول من الموقع أو من تطبيق iOS.
data
{
"post_id": "6f1c2a8e-4b1d-4c55-9a0e-2d7b3f9c1a42",
"cancelled_by": "2a7e4c1b-9f3d-4b6a-8c2e-5d1f0a9b7c34",
"post_status": "draft",
"accounts": [
{
"account_id": "b3e9d1f0-7a2c-4e8b-9c4d-1f6a5e2b8c73",
"platform": "instagram",
"username": "rawwij"
}
],
"still_publishing": 0,
"already_published": 0
}الحسابات
account.connectedرُبط الحسابعند ربط حساب جديد، أو إعادة ربط حساب موجود في مساحة العمل.
data
{
"account_id": "b3e9d1f0-7a2c-4e8b-9c4d-1f6a5e2b8c73",
"platform": "tiktok",
"username": "rawwij",
"reconnected": false,
"connected_by": "2a7e4c1b-9f3d-4b6a-8c2e-5d1f0a9b7c34"
}account.disconnectedانقطع الحسابعند فصل حساب من روّج، أو من المنصة نفسها: إزالة روّج من إعدادات الحساب، أو طلب حذف البيانات، أو سحب الصلاحية.
data
{
"account_id": "b3e9d1f0-7a2c-4e8b-9c4d-1f6a5e2b8c73",
"platform": "facebook",
"username": "Rawwij",
"source": "platform",
"reason": "app_removed_on_platform",
"disconnected_by": null
}account.token_expiringالحساب على وشك الانتهاءعندما ينتهي ربط حساب خلال 7 أيام ولا يمكن تجديده تلقائياً (لينكدإن كل 60 يوماً). يُرسل مرة واحدة لكل انتهاء.
data
{
"account_id": "b3e9d1f0-7a2c-4e8b-9c4d-1f6a5e2b8c73",
"platform": "linkedin",
"username": "Rawwij",
"expires_at": "2026-10-05T11:00:00.000Z",
"days_left": 6
}account.expiredانتهت صلاحية الحسابعندما يتوقف ربط حساب عن العمل ويحتاج إعادة ربط. يتوقف النشر إليه.
data
{
"account_id": "b3e9d1f0-7a2c-4e8b-9c4d-1f6a5e2b8c73",
"platform": "youtube",
"username": "Rawwij",
"reason": "Token has been expired or revoked."
}الفريق
team.member_invitedدعوة عضوعند دعوة شخص إلى مساحة العمل، أو إعادة إرسال دعوة.
data
{
"team_id": "4e8c1a6d-2b9f-4d7e-b3a1-7c5e0d2f9b16",
"invite_id": "7b2f9e4c-6a1d-4c8b-9e3f-2d5a8c1b0e67",
"email": "sara@example.com",
"role": "editor",
"invited_by": "2a7e4c1b-9f3d-4b6a-8c2e-5d1f0a9b7c34",
"expires_at": "2026-10-08T09:00:00.000Z",
"resent": false
}team.member_joinedانضمام عضوعند قبول شخص للدعوة وانضمامه إلى مساحة العمل.
data
{
"team_id": "4e8c1a6d-2b9f-4d7e-b3a1-7c5e0d2f9b16",
"user_id": "9d4b2e7a-1c8f-4e3b-a6d5-0f2c7b1e4a98",
"email": "sara@example.com",
"role": "editor"
}team.member_role_changedتغيير دور عضوعند تغيير دور عضو. نقل الملكية يرسل حدثين: واحد للمالك الجديد وآخر للمالك السابق.
data
{
"team_id": "4e8c1a6d-2b9f-4d7e-b3a1-7c5e0d2f9b16",
"user_id": "9d4b2e7a-1c8f-4e3b-a6d5-0f2c7b1e4a98",
"email": "sara@example.com",
"old_role": "editor",
"new_role": "admin",
"changed_by": "2a7e4c1b-9f3d-4b6a-8c2e-5d1f0a9b7c34"
}team.member_removedخروج عضو أو إزالتهعند إزالة عضو، أو مغادرته بنفسه، أو إزالته تلقائياً، مثلاً عند تغيير الخطة وتقليل عدد المقاعد.
data
{
"team_id": "4e8c1a6d-2b9f-4d7e-b3a1-7c5e0d2f9b16",
"user_id": "9d4b2e7a-1c8f-4e3b-a6d5-0f2c7b1e4a98",
"email": "sara@example.com",
"role": "editor",
"reason": "removed",
"removed_by": "2a7e4c1b-9f3d-4b6a-8c2e-5d1f0a9b7c34"
}الاستخدام
usage.thresholdالاستخدام عند 80% و100%عندما تصل مساحة العمل إلى 80% أو 100% من منشورات X الشهرية أو من المساحة، أو يصل عضو إلى 80% أو 100% من تعليقات الذكاء الاصطناعي الشهرية. مرة واحدة لكل مستوى في كل فترة، ويُفحص كل ساعة.
data
{
"metric": "x_posts",
"threshold": 80,
"used": 320,
"limit": 400,
"unit": "posts",
"user_id": null,
"period_start": "2026-10-01T00:00:00.000Z",
"period_end": "2026-11-01T00:00:00.000Z"
}التحليلات
analytics.dailyالتحليلات اليوميةمرة يومياً بعد الساعة 07:00 بتوقيت UTC: المتابعون والتفاعل ومرات الظهور والوصول لأمس، لكل حساب مرتبط يوفّرها. الرقم الذي لا توفره المنصة يأتي null وليس 0. يوتيوب ولينكدإن وتيليجرام غير مشمولة.
data
{
"date": "2026-09-30",
"accounts": [
{
"account_id": "b3e9d1f0-7a2c-4e8b-9c4d-1f6a5e2b8c73",
"platform": "instagram",
"username": "rawwij",
"followers": 12480,
"engagement": 922,
"impressions": 18230,
"reach": 9412,
"synced_at": "2026-10-01T00:15:40.000Z"
},
{
"account_id": "c8a1f4e2-3d5b-4a9c-8e7f-6b2d1c9a0e51",
"platform": "tiktok",
"username": "rawwij",
"followers": 3310,
"engagement": 1343,
"impressions": 40210,
"reach": null,
"synced_at": "2026-10-01T00:15:52.000Z"
}
]
}ممارسات جيدة
- أجب بـ 2xx أولاً ثم نفّذ العمل بعدها، حتى لا تبدو المهمة البطيئة فشلاً.
- تجاهل التكرار بالاعتماد على id. قد توصل إعادة المحاولة بعد انتهاء المهلة حدثاً عالجته من قبل.
- لا تعتمد على ترتيب الوصول، واستخدم created_at لترتيب الأحداث.
- تحقق من كل توقيع، وتعامل مع المفتاح السري ككلمة مرور.
- تجاهل الحقول التي لا تعرفها. قد تُضاف حقول جديدة، أما الحالية فلن يتغير معناها.