Webhooks للمطورين
متاح فيFreeProResearch
يجعل الـ webhook خادم Readsira يرسل طلب HTTP من نوع POST إلى خادمك عندما يحدث شيء في حسابك،
مثل حفظ إشارة مرجعية. هذه الصفحة موجهة للكود الذي يستقبله: الأحداث والحمولة والتوقيع وقواعد
إعادة المحاولة. لإضافة webhook من التطبيق، راجع
Webhooks.
قبل أن تبدأ
- عنوان
httpأوhttpsعام يقبلPOST. يرفض Readsiralocalhostوعناوين الشبكات الخاصة، ولا يتبع عمليات إعادة التوجيه. - يجب أن يقرأ خادمك جسم الطلب الخام. تحتاج إلى البايتات نفسها للتحقق من التوقيع.
كيف يبدو التسليم
كل تسليم هو POST بجسم JSON وثلاث ترويسات:
| الترويسة | القيمة |
|---|---|
content-type | application/json |
x-readsira-event | اسم الحدث، مثل bookmark.created |
x-readsira-signature | sha256= متبوعة بقيمة HMAC للجسم بصيغة سداسية عشرية |
الجسم غلاف:
{
"event": "bookmark.created",
"timestamp": "2026-11-04T09:15:02.481Z",
"data": {
"bookmark_id": "c71d0e3a-5b1c-4c58-9d6f-0f6c0d2a9e11",
"user_id": "7b0e1f42-2a43-4f0b-8f0e-1d2c3b4a5f60",
"url": "https://example.com/grid-lab",
"title": "Grid Lab: long-duration storage",
"tags": ["energy"],
"collection_id": "a2c4e6f8-1b3d-4f5a-8c7e-9d0b1a2c3d4e"
}
}
timestamp هو وقت بناء Readsira للتسليم. القيم أعلاه مثال.
الأحداث
تختار الأحداث لكل webhook. يحصل أي webhook جديد على bookmark.created.
| الحدث | يُرسل عندما | data |
|---|---|---|
bookmark.created | تحفظ إشارة مرجعية | bookmark_id, user_id, url, title, tags, collection_id |
bookmark.updated | تغيّر إشارة مرجعية | bookmarkId, userId, bookmark, changes, notesChanged |
bookmark.deleted | تُحذف إشارة مرجعية | bookmarkId, userId, bookmark |
bookmark.visited | تفتح إشارة مرجعية | bookmarkId, userId, visitedAt |
output.created | ينتهي ناتج مشروع | id, projectId, kind, title, version, sourceCount, completedAt |
يعرض التطبيق أيضًا collection.created وcollection.updated وcollection.deleted. لا يرسل Readsira
هذه الأحداث بعد، فالـ webhook الذي يستمع إليها فقط لا يستقبل شيئًا.
في bookmark.updated وbookmark.deleted، يكون bookmark لقطة تحتوي على id وurl وtitle
وdescription وcollectionId وtags وisStarred وisPublic وdomain وnotes وtriageStatus
وcaptureIntent. يسرد changes أسماء الحقول التي ضبطها الطلب. أما في output.created
فتحمل الحمولة المعرّفات والبيانات الوصفية فقط، ولا تحمل نص الناتج أبدًا؛ اقرأه من التطبيق.
يرسل الزر اختبار في التطبيق حدثًا إضافيًا واحدًا هو webhook.test، مع
data: { "message": "Test webhook from Readsira", "userId": "..." }. ويكون موقَّعًا مثل غيره.
تتوقف الـ webhooks مؤقتًا بينما يكون حسابك مجدولًا للحذف.
التحقق من التوقيع
يثبت التوقيع أن الطلب جاء من Readsira وأن أحدًا لم يغيّر الجسم. يحسب Readsira
HMAC-SHA256 على الجسم الخام، بسر الـ webhook مفتاحًا، ويرسل الناتج
بصيغة sha256= متبوعة بـ 64 خانة سداسية عشرية.
السر هو النص السداسي العشري المكوّن من 64 خانة الذي يعرضه التطبيق عند إنشاء الـ webhook أو تدوير سره. استخدم هذا النص كما هو، بصفته نصًّا. لا تفكّ ترميزه من الصيغة السداسية العشرية. يظهر مرة واحدة فقط.
مستقبِل في Node، دون أي حزم:
import { createHmac, timingSafeEqual } from 'node:crypto';
import { createServer } from 'node:http';
const SECRET = process.env.READSIRA_WEBHOOK_SECRET;
function isFromReadsira(rawBody, signatureHeader) {
const expected = 'sha256=' + createHmac('sha256', SECRET).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader ?? '');
return a.length === b.length && timingSafeEqual(a, b);
}
createServer((req, res) => {
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => {
const rawBody = Buffer.concat(chunks);
if (!isFromReadsira(rawBody, req.headers['x-readsira-signature'])) {
res.writeHead(401).end();
return;
}
const { event, timestamp, data } = JSON.parse(rawBody.toString('utf8'));
console.log(event, timestamp, data);
res.writeHead(204).end();
});
}).listen(3000);
ثلاث تفاصيل تسبب معظم الإخفاقات:
- احسب التجزئة على البايتات الخام. إذا حلّل إطار العمل لديك JSON أولًا ثم أعدت تسلسله، تختلف
البايتات ويفشل الفحص. في Express، استخدم
express.raw({ type: 'application/json' })لمسار الـ webhook. - قارن في زمن ثابت، كما تفعل
timingSafeEqual، وليس بـ===. - بعد تدوير السر في التطبيق، تفشل في الفحص الطلبات الموقَّعة بالسر القديم. حدّث
READSIRA_WEBHOOK_SECRETأولًا.
لرفض الطلبات المعاد إرسالها، قارن timestamp بالساعة وتجاهل عمليات التسليم الأقدم من بضع
دقائق. الطابع الزمني داخل الجسم الموقَّع، فلا يمكن تغييره دون كسر
التوقيع. تحمل إعادة المحاولة (أدناه) الحدث الأصلي نفسه مرة أخرى، فاجعل معالجك آمنًا عند تشغيله مرتين.
أجب بسرعة
أجب بأي حالة 2xx فور تخزين الحدث. ينتظر Readsira 5 ثوانٍ ويقرأ 64 KB كحد أقصى من ردك.
| إجابتك | ما يفعله Readsira |
|---|---|
2xx | يُعد التسليم مُسلَّمًا. |
5xx أو لا إجابة خلال 5 ثوانٍ | يُعاد التسليم، حتى 4 محاولات في المجموع، مع انتظار بينها يبدأ من 30 ثانية ويتضاعف. |
4xx | يُعد التسليم فاشلًا ولا يُعاد. |
يعرض التطبيق لكل webhook عدد مرات إطلاقه ووقت آخر إطلاق.
نصائح
- اجعل المعالج قصيرًا. ضع العمل في طابور وأجب أولًا.
- استخدم الزر اختبار أثناء البناء، قبل أن تحفظ إشارة مرجعية حقيقية.
- احفظ السر كما تحفظ كلمة المرور. دوّره إن تسرّب.
ذات صلة
- Webhooks (الإعداد في التطبيق)
- أمثلة
- حدود المعدل والأخطاء