التحقق من تسليم
`verifyWebhookSignature`: مقارنة بزمن ثابت، مع نافذة إعادة تشغيل، وإعادة الحدث محلَّلًا.
داخل معالج طلب
عنوان URL الخاص بـwebhook عام. فأي شيء على الإنترنت يستطيع أن يرسل إليه بـPOST بنية JSON بالشكل الصحيح، ولذلك فإن معالجًا يقرأ payload.type دون التحقق من التوقيع هو API كتابة مفتوح للجميع.
import { verifyWebhookSignature } from '@openemail/sdk' export async function POST(request: Request) { try { const event = await verifyWebhookSignature({ payload: await request.text(), headers: request.headers, secret: process.env.OPENEMAIL_WEBHOOK_SECRET!, toleranceSeconds: 300, }) console.log(event.type, event.data) } catch { return new Response('bad signature', { status: 400 }) } return new Response(null, { status: 204 })}مرّر المتن الخام. فالتحليل وإعادة التسلسل يغيّران ترتيب المفاتيح والمسافات، ولن يتطابق التوقيع. ويأخذ headers كائن Headers أو كائنًا عاديًا مثل req.headers في Node، ويُعثر على X-OpenEmail-Signature مهما كانت حالة أحرفه.
أمران يعالجهما هذا ولا يعالجهما عادةً فحص مكتوب يدويًا: فهو يقارن الـMAC بزمن ثابت، فلا يمكن استخراج البادئة الصحيحة بقياس الزمن، وهو يرفض تسليمًا أقدم من toleranceSeconds في أي من الاتجاهين، أي خمس دقائق ما لم تقل غير ذلك، فلا يبقى طلب ملتقَط قابلًا لإعادة التشغيل إلى الأبد. والقيمة toleranceSeconds: 0 تعطّل فحص إعادة التشغيل. وكلا الخللين صامت. والمعالج المصاب بأي منهما يجتاز كل اختبار قد يخطر لك أن تكتبه.
يرمي استثناءً عند كل إخفاق: ترويسة X-OpenEmail-Signature مفقودة، أو ترويسة ليست على الصيغة t=<seconds>,v1=<hex>، أو طابع وقت خارج النافذة، أو توقيع لا يتطابق. وعند النجاح يُحَل إلى المتن محلَّلًا كـWebhookPayload، فلا يوجد JSON.parse ثانٍ يمكن أن تخطئ فيه. ومرّر وسيط نوع مثل EmailOpenedData لتحديد نوع data.
يحتاج إلى globalThis.crypto.subtle، وهو ما يوفّره Node 20+ وBun وDeno وCloudflare Workers جميعًا.