كيفية التحقق من webhook في Qvard

دليل سريع عن رأس X-Qvard-Signature: ما هو وكيفية التحقق منه بعدة أسطر من الكود.

ما هي التوقيع

عندما تتغير حالة الدفع، يرسل Qvard إخطاراً (webhook) إلى العنوان في إعدادات متجرك. يمكن لأي شخص إرسال طلب إلى هذا العنوان، لذا تحتاج إلى طريقة للتمييز بين إخطار حقيقي وآخر مزيف.

التوقيع هو هذا الإثبات: رمز قصير يحسبه Qvard من الرسالة ومفتاحك السري. فقط Qvard وأنت تعرفان المفتاح، لذا فقط Qvard يمكنه إنتاج الرمز الصحيح.

ما الذي تتلقاه

يحمل كل webhook رأس إضافي واحد وجسم JSON صغير:

X-Qvard-Signature: t=1760950000,v1=3f2a…c91e {"id":"order_1024","status":"paid"}

t هو الوقت الذي تم إرسال الرسالة (Unix، ثوان). v1 هو الرمز. لمدة 24 ساعة بعد تدوير مفتاحك السري، يحمل الرأس قيمتي v1: واحدة للمفتاح الجديد وواحدة للمفتاح القديم.

تبقى الحقول id وstatus كما كانت. وتُضاف أربعة حقول مالية إلى نفس الجسم الموقَّع:

  • amount وcurrency: المبلغ والعملة كما أرسلتهما في الطلب.
  • amountUsd: المبلغ المحاسبي بالدولار الأمريكي.
  • rate: عدد وحدات العملة مقابل 1 دولار أمريكي وقت إنشاء الدفعة.
  • التكاملات القديمة تواصل العمل: تقرأ id وstatus وتتجاهل الحقول الجديدة. ويُتحقق من التوقيع على نفس الجسم كما في السابق.

كيفية التحقق منها

  1. 1
    خذ جسم الطلب تماماً كما وصل: النص الخام، قبل أي تحليل JSON.
  2. 2
    اقرأ t وكل v1 من رأس X-Qvard-Signature.
  3. 3
    احسب HMAC-SHA256 للنص t + "." + الجسم بمفتاحك السري (qvard_sk_…) واكتبه كسادس عشر بأحرف صغيرة.
  4. 4
    إذا كانت نتيجتك تطابق أي v1 (قارن في وقت ثابت)، الرسالة حقيقية.
  5. 5
    تحقق من أن t ليس أقدم من 5 دقائق، لذلك لا يمكن إعادة تشغيل رسالة قديمة.
  6. 6
    فقط بعد ذلك استخدم البيانات وتأكد من مبلغ الدفع لهذا الطلب في قاعدة البيانات الخاصة بك.

الكود جاهز

const crypto = require('crypto');

function verifyQvardSignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = header.split(',').map((p) => p.trim());
  const t = Number((parts.find((p) => p.startsWith('t=')) || '').slice(2));
  const sigs = parts.filter((p) => p.startsWith('v1=')).map((p) => p.slice(3));
  if (!t || sigs.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  return sigs.some((s) => {
    const got = Buffer.from(s, 'hex');
    return got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
}

// Use the RAW body: express.raw, not express.json
app.post('/qvard-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyQvardSignature(raw, req.get('X-Qvard-Signature') || '', process.env.QVARD_SECRET_KEY)) {
    return res.sendStatus(400);
  }
  const { id, status } = JSON.parse(raw);
  // Confirm the paid amount for order `id` in your own database before fulfilling it.
  res.sendStatus(200);
});

لمدة 24 ساعة بعد تدوير مفتاحك السري، يحمل الرأس قيمتي v1: اقبل webhook إذا كانت أي منهما تطابق.

الأخطاء الشائعة

  • يؤدي تحليل JSON وتسلسله مرة أخرى إلى تغيير النص. استخدم دائماً الجسم الخام.
  • استخدام المفتاح العام (qvard_pk_…) بدلاً من المفتاح السري.
  • ساعة الخادم غير المتزامنة تجعل فحص 5 دقائق يفشل. حافظ عليها متزامنة.
  • المقارنة مع == بدلاً من مقارنة الوقت الثابت.

أين تجد مفتاحك السري

في لوحة التحكم: متجرك، الإعدادات، مفاتيح API. احفظه فقط على خادمك. إذا تسرب، قم بتدويره هناك: المفتاح القديم يستمر في العمل لمدة 24 ساعة للدفع (وليس للسحب).

فتح لوحة التحكم

مرجع API

المفاتيح القديمة

إذا كان لدى متجرك لا يزال مفتاح تنسيق قديم (بدون بادئة qvard_sk_)، فإن webhooks تحمل أيضاً رأس x-api-key بهذا المفتاح. بعد تدويره، يختفي هذا الرأس. انتقل إلى التحقق من التوقيع.

هل تريد تجربة نافذة الدفع؟

فتح العرض التوضيحي