الرابط الأساسي https://api.echonova.ai

المطورون

التحقق من توقيع Webhook

أعد حساب توقيع HMAC-SHA256 في كل تسليم، وقارنه في وقت ثابت، وارفض الطلبات القديمة أو غير الموقعة.

يستطيع أي شخص يعرف رابط نقطة نهايتك أن يرسل إليها JSON. يثبت التوقيع على كل تسليم أن الطلب صادر عن إيكونوڤا، وأن الجسم لم يتغير في الطريق. تحقق منه قبل أن تتصرف بناءً على الحدث.

ما تحتاج إليه

  • Webhook وسر توقيعه، وهو النص whsec_ الذي يُعاد مرة واحدة عند الإنشاء. لا يُعرض مرة أخرى، لذلك احفظه عند إنشاء Webhook؛ راجع حفظ سر التوقيع.
  • وصول إلى الجسم الخام للطلب على خادمك. الأطر التي تحلل JSON قبل أن يعمل كودك يجب أن تُخبر بالاحتفاظ بالبايتات الأصلية.
  • ثلاث headers في كل تسليم، مذكورة في أحداث Webhook.

الخطوات

الخطوة 1: اقرأ الجسم الخام ثم headers

خذ جسم الطلب كما وصل تمامًا، كبايتات. لا تحلله ثم تعيد تسلسله: أي تغيير في ترتيب المفاتيح أو المسافات أو ترميز Unicode ينتج توقيعًا مختلفًا. اقرأ X-Webhook-Timestamp وX-Webhook-Signature من headers.

موصى به

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const rawBody = req.body; // Buffer, byte for byte as it arrived
});

أقل فاعلية

app.post('/webhook', express.json(), (req, res) => {
  const rawBody = JSON.stringify(req.body); // re-serialised; the signature no longer matches
});

الخطوة 2: ابنِ السلسلة الموقّعة

اربط الطابع الزمني بالجسم بنقطة واحدة:

<X-Webhook-Timestamp>.<raw body>

الطابع الزمني هو السلسلة العشرية من header. الجسم هو البايتات الخام. لا يدخل شيء آخر: لا اسم الحدث، ولا الرابط، ولا headers الأخرى.

الخطوة 3: احسب التوقيع المتوقع

احسب HMAC-SHA256 على السلسلة الموقّعة، بالمفتاح كاملًا، أي سر التوقيع بكامل بادئته whsec_، ثم حوّل الناتج إلى سداسي عشري بأحرف صغيرة. يحمل header X-Webhook-Signature السلسلة السداسية نفسها، بطول 64 حرفًا.

الخطوة 4: قارن في وقت ثابت

قارن نتيجتك مع header باستخدام دالة بوقت ثابت: crypto.timingSafeEqual في Node، وhmac.compare_digest في Python. المقارنة النصية العادية تتوقف عند أول بايت مختلف، وقد يسرّب فرق التوقيت السر لمهاجم صبور. ارفض الطلب بالرمز 401 عندما تختلف القيمتان أو يغيب header.

الخطوة 5: ارفض الطوابع الزمنية القديمة

ارفض التسليم الذي يبعد X-Webhook-Timestamp عن ساعة خادمك بأكثر من 300 ثانية، في أي اتجاه. النافذة تختارها أنت، و300 ثانية هي القيمة الموصى بها. وإلا فقد يُعاد تشغيل طلب موقّع التقطه مهاجم لاحقًا. كل إعادة تحمل طابعًا زمنيًا وتوقيعًا جديدين، والمحاولة الثالثة والأخيرة تبدأ في أقصى حد بعد نحو 35 ثانية من الأولى، لذلك لا ترفض نافذة 5 دقائق الإعادات المشروعة. حافظ على مزامنة ساعة خادمك عبر NTP.

الخطوة 6: أقرّ أولًا، ثم اعمل

أعد الرمز 2xx فور نجاح التوقيع، ثم عالج الحدث. تنتظر إيكونوڤا 10 ثوانٍ للرد، وتعد أي شيء آخر محاولة فاشلة.

import hashlib
import hmac
import os
import time

from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["ECHONOVA_WEBHOOK_SECRET"].encode()  # whsec_...
MAX_AGE_SECONDS = 300  # recommended window; the choice is yours


def verify(raw_body: bytes, headers) -> bool:
    signature = headers.get("X-Webhook-Signature", "")
    timestamp = headers.get("X-Webhook-Timestamp", "")
    if not timestamp.isdigit():
        return False
    if abs(int(time.time()) - int(timestamp)) > MAX_AGE_SECONDS:
        return False
    signed = timestamp.encode() + b"." + raw_body
    expected = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected.encode(), signature.encode())


@app.post("/webhook")
def webhook():
    if not verify(request.get_data(), request.headers):
        abort(401)
    event = request.headers["X-Webhook-Event"]
    payload = request.get_json(force=True)
    # Hand the event to a queue here, then answer.
    return "", 200

ماذا يحدث بعد ذلك

  • يكون الطلب الذي يجتاز التحقق تسليمًا حقيقيًا من إيكونوڤا، بجسم لم يتغير. اقرأ header X-Webhook-Event لتختار المعالج، ثم حلّل JSON.
  • الطلب الذي يفشل يحصل على 401 ولا تجري معالجة بعده. الرد غير 2xx محاولة فاشلة تُعاد، فالتوقيع الذي لا يتطابق أبدًا ينتهي تسليمًا فاشلًا، و5 تسليمات فاشلة متتالية تعطّل Webhook. في هذه الحالة أصلح السر، ثم فعّل Webhook من جديد.

الحدود

الحد القيمة ينطبق على
التوقيع HMAC-SHA256، 64 حرفًا سداسيًا عشريًا بأحرف صغيرة X-Webhook-Signature
الطابع الزمني ثوانٍ Unix كاملة X-Webhook-Timestamp
مفتاح التوقيع السر كاملًا، أي whsec_ مع 43 حرفًا مفتاح HMAC
مهلة الرد 10 ثوانٍ لكل محاولة نقطة النهاية لديك
نافذة الصلاحية 300 ثانية، موصى بها أداة التحقق لديك

استكشاف المشكلات

لا يتطابق التوقيع أبدًا

أكثر الأسباب شيوعًا، بالترتيب: حلّل إطارك الجسم وأعاد تسلسله قبل أن تجزّئه؛ أو جزّأت الجسم دون البادئة <timestamp>.؛ أو استخدمت البادئة المكونة من 12 حرفًا بدلًا من السر الكامل؛ أو السر يخص Webhook آخر. سجّل البايتات الخام التي جزّأتها والطابع الزمني الذي استخدمته، وقارن أطوالها بما وصل.

كل طلب يُرفض لأنه قديم

ساعة خادمك غير دقيقة. تحقق منها عبر NTP. إذا كان معالجك خلف طابور، فتحقق من التوقيع عند الحافة، قبل وضع الحدث في الطابور، حتى يستخدم الفحص وقت الوصول.

الإعادة لها توقيع مختلف

متوقع. يُوقَّع كل محاولة لحظة إرسالها، بطابع زمني جديد. الجسم متطابق عبر المحاولات، لذلك ما زال بإمكانك إزالة التكرار بالاعتماد عليه.

إطاري يعطيني JSON محللًا فقط

اضبطه ليحتفظ بالجسم الخام لمسار Webhook. في Express، ركّب express.raw({ type: 'application/json' }) على ذلك المسار قبل أي محلل JSON. في Flask، تعيد request.get_data() البايتات الخام ما دمت تستدعيها قبل request.get_json().

الأسئلة الشائعة

أي سر أستخدم؟

السر الكامل signingSecret الذي أُعيد عند إنشاء Webhook، بما في ذلك البادئة whsec_. أما signingSecretPrefix المكون من 12 حرفًا فهو تسمية وليس مفتاحًا.

هل يدخل اسم الحدث في التوقيع؟

لا. يُوقَّع الطابع الزمني والجسم فقط. اقرأ اسم الحدث من X-Webhook-Event، وإذا احتجت، تحقق منه مقابل الحقول في الجسم.

هل أستطيع التحقق باستخدام cURL؟

لا. لا يستطيع cURL حساب HMAC. استخدم عينة Python أو Node أعلاه، أو أي لغة فيها دالة HMAC-SHA256.

هل أستطيع تدوير السر؟

السر ثابت طوال عمر Webhook. أنشئ Webhook ثانيًا لنفس الرابط والأحداث، وانشر سره، ثم احذف الأول. خلال التداخل يجب أن تقبل نقطة نهايتك أيًّا من السرين.

ماذا لو لم أتحقق؟

يستطيع أي شخص يجد رابطك أن يرسل job.completed مزيفًا بالرابط الذي يختاره، أو spending.exhausted مزيفًا يجعلك تعطّل مفتاحًا. عامل التسليم غير المتحقق منه كمدخل غير موثوق.