إنتقل إلى المحتوى الرئيسي

Webhooks للمطورين

متاح فيFreeProResearch

يجعل الـ webhook خادم Readsira يرسل طلب HTTP من نوع POST إلى خادمك عندما يحدث شيء في حسابك، مثل حفظ إشارة مرجعية. هذه الصفحة موجهة للكود الذي يستقبله: الأحداث والحمولة والتوقيع وقواعد إعادة المحاولة. لإضافة webhook من التطبيق، راجع Webhooks.

قبل أن تبدأ​

  • عنوان http أو https عام يقبل POST. يرفض Readsira localhost وعناوين الشبكات الخاصة، ولا يتبع عمليات إعادة التوجيه.
  • يجب أن يقرأ خادمك جسم الطلب الخام. تحتاج إلى البايتات نفسها للتحقق من التوقيع.

كيف يبدو التسليم​

كل تسليم هو POST بجسم JSON وثلاث ترويسات:

الترويسةالقيمة
content-typeapplication/json
x-readsira-eventاسم الحدث، مثل bookmark.created
x-readsira-signaturesha256= متبوعة بقيمة 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 عدد مرات إطلاقه ووقت آخر إطلاق.

نصائح​

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