اختر Auto أو TokenLab Verified أو Official لكل طلب، مع عرض الأسعار مسبقاً. اطلع على الجديد

خطافات الويب (Webhooks) لمهام الذكاء الاصطناعي غير المتزامنة: تحقق من التوقيع، ثم اقرأ المهمة

CryptoCrypto
·٢٨ سبتمبر ٢٠٢٦·4 دقائق قراءة·آخر تحديث ٢٨ سبتمبر ٢٠٢٦·28 مشاهدة
#خطافات الويب#المهام غير المتزامنة#تكامل واجهة برمجة التطبيقات#الأمان
خطافات الويب (Webhooks) لمهام الذكاء الاصطناعي غير المتزامنة: تحقق من التوقيع، ثم اقرأ المهمة

خطاف الويب (Webhook) هو إشارة موقعة تفيد بأن المهمة قد وصلت إلى حالة نهائية. إنه ليس السجل بحد ذاته. لذا فإن القاعدة مختصرة: تحقق من البايتات الخام، وقم بإلغاء التكرار باستخدام معرف الحدث (event ID)، وأجب بـ 2xx بسرعة، ثم اقرأ GET /v1/tasks/{id} للحصول على النتيجة وحالة الفوترة.

تم إطلاق خطافات ويب مهام مساحة العمل في 2026-09-27. ستحصل على Management API لدورة حياة خطاف الويب، واختبار التسليم، وتدوير الأسرار (secret rotation)، وسجل التسليم. لوحة التحكم وإدارة MCP متاحة أيضاً.

تصحيح واحد في البداية. ذكر دليلنا السابق لإنشاء الصور غير المتزامن أن TokenLab لم يكن لديه رد اتصال للمهام؛ كان ذلك صحيحاً قبل 2026-09-27، وقد تم تحديث ذلك الدليل جنباً إلى جنب مع هذا الدليل.

خطافات الويب أم الاستطلاع (Polling)؟ استخدم كلاهما

إنهما يحلان مشكلات مختلفة، ولا يغني أحدهما عن الآخر.

الموقف استخدم
تريد التفاعل في اللحظة التي تنتهي فيها المهمة Webhook
تحتاج إلى النتيجة الموثوقة أو التكلفة GET /v1/tasks/{id}
كان جهاز الاستقبال الخاص بك معطلاً لفترة الاستطلاع (Polling) باستخدام معرفات المهام المخزنة
تريد خياراً احتياطياً عند اختفاء عمليات التسليم الاستطلاع على فترات زمنية متباعدة

لا تلغي خطافات الويب استعلامات الحالة ولا تضيف حداً للاستطلاع. احتفظ بكليهما. حتى مع تفعيل خطافات الويب، فإن حلقة المراجعة البطيئة التي تقرأ معرفات المهام المخزنة لديك تعد تأميناً رخيصاً.

إذا كنت تستخدم الاستطلاع، فاستخدم poll_url، وتوقف مؤقتاً بينما تكون المهمة معلقة، وتوقف عند الحالات النهائية. توقف عند 401 أو 403 أو 404 أو عندما تكون error.retryable == false. أعد المحاولة عند الخطأ 503 async_task_owner_unavailable مع التراجع (backoff). المهمة المفقودة أو منتهية الصلاحية تعيد 404 async_task_not_found. راجع دليل الوظائف غير المتزامنة والاستطلاع لمعرفة عقد الاستطلاع.

ثلاثة بيانات اعتماد، ثلاث وظائف

خلط هذه البيانات هو أسرع طريق لكسر جهاز الاستقبال.

بيان الاعتماد البادئة ماذا يفعل ملاحظات
Management Token mt-… ينشئ ويسرد ويحدث ويحذف ويختبر ويدور خطافات الويب على /v1/management/webhooks* يُرسل كـ Authorization: Bearer mt-…. مخصص لمساحة العمل
API key sk-… يقدم طلبات النموذج ويقرأ حالة المهمة عبر GET /v1/tasks/{id} مرفوض من قبل Management API
Signing secret whsec_… يتحقق من عمليات التسليم على جهاز الاستقبال الخاص بك ليس أبداً Bearer token

أمران بخصوص Management Token. أولاً، هو يصرح أيضاً بعمليات إدارة مساحة العمل الأخرى، لذا فهو ليس بيان اعتماد لخطافات الويب فقط. اختر نفس مساحة العمل الخاصة بـ API key الذي يقدم مهامك. ثانياً، يمكنك إنشاؤه في لوحة التحكم (Dashboard) ← API ← Management Tokens. راجع مثالاً آخر على Management API.

احتفظ بـ mt-… و whsec_… على الواجهة الخلفية (backend) الخاصة بك فقط. لا ترسل أياً منهما أبداً إلى متصفح أو عميل جوال.

أنشئ نقطة نهاية وقم بتخزين السر فوراً

يعيد استدعاء الإنشاء 201 مع id خطاف الويب و secret لمرة واحدة يبدأ بـ whsec_…. السرد والحصول والتحديث لا تظهر ذلك السر مرة أخرى. قم بتخزينه في اللحظة التي تراه فيها.

export TOKENLAB_MANAGEMENT_TOKEN="mt-your-management-token"
curl https://api.tokenlab.sh/v1/management/webhooks \
  -H "Authorization: Bearer $TOKENLAB_MANAGEMENT_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://your-app.example/webhooks/tokenlab","events":["task.completed","task.failed","task.timeout"],"description":"Production task results"}'

يمكن إدارة نفس نقاط النهاية بثلاث طرق، وجميعها تعدل نفس الكائنات:

قواعد URL صارمة. يجب أن تكون نقطة النهاية HTTPS عامة. لا يُسمح ببيانات الاعتماد أو سلسلة الاستعلام أو الأجزاء في الـ URL. لا يتم اتباع عمليات إعادة التوجيه، لذا فإن 301 تُحسب كتسليم فاشل.

يمكنك الحصول على ما يصل إلى 10 نقاط نهاية لكل مساحة عمل. الإنشاء الحادي عشر يعيد 409 webhook_limit_reached.

الطريقة المسار الغرض
GET /v1/management/webhooks سرد نقاط النهاية
POST /v1/management/webhooks إنشاء نقطة نهاية
GET /v1/management/webhooks/{webhookId} قراءة نقطة نهاية واحدة
PATCH /v1/management/webhooks/{webhookId} تحديث أو إيقاف مؤقت أو استئناف
DELETE /v1/management/webhooks/{webhookId} حذف
POST /v1/management/webhooks/{webhookId}/rotate-secret تدوير سر التوقيع
POST /v1/management/webhooks/{webhookId}/test إرسال webhook.test
GET /v1/management/webhooks/{webhookId}/deliveries?page=1&limit=50 سجل التسليم، بحد أقصى 100

أوقف مؤقتاً باستخدام PATCH {"is_active": false}. استأنف باستخدام PATCH {"is_active": true}. الاستئناف يعيد تعيين عدد الفشل المتتالي، وهو أمر مهم بعد انقطاع الخدمة.

ما الذي يصل فعلياً

كل تسليم هو POST مع ظرف JSON. حقول Management API تستخدم snake_case، لكن حقول رد الاتصال تستخدم camelCase. لا تفترض أن تنسيقاً واحداً ينتقل إلى الآخر.

الحقل المعنى
id معرف الحدث. استخدمه لإلغاء التكرار
type نوع الحدث
created ثواني Unix
data حمولة الحدث، شكلها يعتمد على الحدث
الحدث يتم إطلاقه عندما
task.completed انتهت المهمة بنجاح
task.failed انتهت المهمة بالفشل
task.timeout وصلت المهمة إلى حدها الزمني
webhook.test يُرسل فقط بواسطة عملية الاختبار

يحمل task.completed كلاً من taskType (على سبيل المثال video أو image)، و taskId، و model اختياري، و durationMs، و resultUrls، و settledCost.

يحمل task.failed كلاً من taskType، و taskId، و error، و errorCode، و retryable، و refundOutcome.

يحمل task.timeout كلاً من taskType، و taskId، و refundOutcome وحقول وقت الانتظار. اقرأ سجل المهمة لتلك القيم؛ مجموعة الحقول تعتمد على المهمة.

تغطي الاشتراكات أحداث النهاية المستقبلية للمهام غير المتزامنة في مساحة العمل. لا يتم إعادة تشغيل النتائج المتزامنة والمهام التاريخية. تتلقى كل مهمة في مساحة العمل لأنواع الأحداث التي اخترتها، لذا طابق data.taskId مع المعرف الذي خزنته عند إنشاء المهمة.

قد تكون الحقول غائبة اعتماداً على المهمة. لهذا السبب يظل GET /v1/tasks/{id} مع مفتاح sk-… الخاص بمساحة العمل الأصلية هو مصدر الحقيقة للنتيجة وحالة الفوترة. يخبرك الحدث أن شيئاً ما قد انتهى. يخبرك سجل المهمة بما أنتجته وما تكلفته.

شيء آخر بخصوص retryable في حدث فاشل. إنه يصف فشل التوليد، وليس تعليمات لإعادة التقديم تلقائياً. التقديم الجديد هو مهمة جديدة قابلة للفوترة.

تحقق من البايتات الخام، ثم عالج مرة واحدة

يحمل كل POST ثلاثة رؤوس:

  • X-Webhook-ID
  • X-Webhook-Timestamp، ثواني Unix
  • X-Webhook-Signature، بتنسيق sha256=

التوقيع هو HMAC-SHA256 عبر سلسلة الطابع الزمني الدقيقة، ونقطة، وبايتات نص الطلب الخام، باستخدام سر whsec_… الكامل. الترتيب مهم، وكذلك النص.

خطآن يكسران فحوصات التوقيع أكثر من أي شيء آخر:

  1. التحقق من JSON الذي تم تحليله. إذا قمت بتحليل النص وإعادة تسلسله، تتغير البايتات ولن يتطابق HMAC. اقرأ النص الخام. احتفظ به كبايتات حتى ينجح التحقق.
  2. التحقق باستخدام سر واحد فقط أثناء التدوير. بعد التدوير، قد تظل عمليات التسليم التي في الطريق تحمل التوقيع السابق. اقبل قائمة من الأسرار لفترة قصيرة.

جهاز استقبال Node أدناه خالٍ من التبعيات ويستخدم node:http. يقرأ النص الخام، ويتحقق مقابل قائمة من الأسرار، ويتحقق من نافذة الـ 300 ثانية، ويقارن id النص بـ X-Webhook-ID، ويزيل التكرار حسب معرف الحدث، ويضعه في قائمة الانتظار، ويعيد 204. إلغاء التكرار في العينة هو مجموعة في الذاكرة؛ استخدم قيد قاعدة بيانات فريد في الإنتاج.

import { createServer } from 'node:http';
import { createHmac, timingSafeEqual } from 'node:crypto';

// أثناء التدوير، ادرج كلاً من السر الجديد والسر السابق whsec_.
const SECRETS = (process.env.TOKENLAB_WEBHOOK_SECRETS ?? '').split(',').filter(Boolean);
const TOLERANCE_SECONDS = 300;
const seen = new Set(); // استخدم قيد قاعدة بيانات فريد في الإنتاج، وليس الذاكرة.

function verify(rawBody, headers) {
  const timestamp = headers['x-webhook-timestamp'];
  const signature = headers['x-webhook-signature'];
  if (typeof timestamp !== 'string' || !/^\d+$/.test(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;
  if (typeof signature !== 'string' || !/^sha256=[a-f0-9]{64}$/.test(signature)) return false;
  const received = Buffer.from(signature.slice(7), 'hex');
  return SECRETS.some((secret) => {
    const expected = createHmac('sha256', secret).update(timestamp + '.').update(rawBody).digest();
    return timingSafeEqual(expected, received);
  });
}

const server = createServer((req, res) => {
  if (req.method !== 'POST' || req.url !== '/webhooks/tokenlab') {
    res.writeHead(404).end();
    return;
  }
  const chunks = [];
  req.on('data', (chunk) => chunks.push(chunk));
  req.on('end', () => {
    const rawBody = Buffer.concat(chunks); // تحقق من البايتات الدقيقة، قبل JSON.parse
    if (!verify(rawBody, req.headers)) {
      res.writeHead(401).end();
      return;
    }
    const event = JSON.parse(rawBody.toString('utf8'));
    if (event.id !== req.headers['x-webhook-id']) {
      res.writeHead(400).end();
      return;
    }
    if (!seen.has(event.id)) {
      seen.add(event.id);
      enqueue(event); // سلم العمل؛ قم بالعمل البطيء خارج الطلب
    }
    res.writeHead(204).end();
  });
});

function enqueue(event) {
  console.log('queued', event.type, event.data?.taskId);
}

server.listen(Number(process.env.PORT ?? 3000));

تم اختبار جهاز الاستقبال محلياً في 2026-09-28 مقابل طلبات موقعة تماماً مثل المرسل في الإنتاج: تسليم صالح، تسليم مكرر، سر سابق أثناء التدوير، سر خاطئ، طابع زمني قديم، عدم تطابق معرف الرأس والنص، نص تم التلاعب به، وJSON معاد تسلسله. ثماني حالات، كلها نجحت. تم وضع مكرر في قائمة الانتظار مرة واحدة.

جانب Python هو دالة تحقق واحدة. تقارن التوقيعات بـ hmac.compare_digest وتتوقع بايتات النص الخام من request.get_data() في Flask أو await request.body() في FastAPI.

import hashlib
import hmac
import re
import time

TOLERANCE_SECONDS = 300
SIGNATURE_RE = re.compile(r"^sha256=[a-f0-9]{64}$")


def verify_webhook(raw_body: bytes, headers, secrets: list[str]) -> bool:
    """تحقق من خطاف ويب TokenLab مقابل سر واحد أو أكثر من أسرار whsec_.

    يجب أن يكون raw_body هو بايتات الطلب الدقيقة (Flask: request.get_data(),
    FastAPI/Starlette: await request.body())، مقروءة قبل أي تحليل JSON.
    """
    timestamp = headers.get("x-webhook-timestamp", "")
    signature = headers.get("x-webhook-signature", "")
    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
        return False
    if not SIGNATURE_RE.match(signature):
        return False
    received = signature.removeprefix("sha256=")
    for secret in secrets:
        expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
        if hmac.compare_digest(expected, received):
            return True
    return False

تم الاختبار في 2026-09-28: صالح، سر سابق، سر خاطئ، طابع زمني قديم، نص تم التلاعب به، ونص معاد تسلسله بتباعد json.dumps الافتراضي. ست حالات، كلها نجحت.

بعيداً عن التوقيع، قم بثلاثة أشياء في كل طلب:

  • ارفض الطوابع الزمنية التي تزيد عن 300 ثانية من الآن. هذا يعني 5 دقائق، وهو يحدد مدى قدم إعادة التشغيل.
  • تأكد من أن id النص يساوي X-Webhook-ID.
  • خزن معرف الحدث مع عنصر العمل الخاص بك في كتابة ذرية واحدة، مدعومة بقيد فريد. ثم أعد 2xx بسرعة وقم بالعمل الشاق من قائمة الانتظار الخاصة بك.

يمكن أن تتكرر عمليات التسليم ولا نضمن الترتيب. تحد نافذة الطابع الزمني من عمر إعادة التشغيل. يمنع إلغاء تكرار معرف الحدث المعالجة المزدوجة.

إعادة المحاولات، الإيقاف التلقائي، وكتيب استعادة الخدمة

تقوم كل دورة تسليم بما يصل إلى ثلاث محاولات.

المحاولة الانتظار قبلها مهلة المحاولة
1 لا يوجد 10 ثوانٍ
2 1 ثانية 10 ثوانٍ
3 4 ثوانٍ 10 ثوانٍ

المصدر: دليل خطاف ويب TokenLab، تمت ملاحظته في 2026-09-28.

تحصل كل محاولة على طابع زمني وتوقيع جديدين. هذا يعني أن فحص التوقيع الخاص بك يجب أن يستخدم الطابع الزمني من نفس الطلب، وليس قيمة مخزنة مؤقتاً.

الاستجابات القابلة لإعادة المحاولة: فشل الشبكة، 429، و 5xx. لا تتم إعادة المحاولة ضمن الدورة: 4xx الأخرى، وإعادة التوجيه، وأهداف الشبكة غير الصالحة. يمكن أن تؤدي حالات الفشل المؤقتة إلى إعادة محاولات لاحقة لنفس الحدث بنفس معرف التسليم، وهو سبب آخر لعدم كون إلغاء التكرار اختيارياً.

عشر دورات فشل متتالية توقف نقطة النهاية تلقائياً.

عندما كان جهاز الاستقبال الخاص بك معطلاً، اعمل من خلال هذا بالترتيب:

  1. أصلح جهاز الاستقبال. تأكد من أنه يقرأ البايتات الخام ويعيد 2xx بسرعة.
  2. استأنف نقطة النهاية باستخدام PATCH {"is_active": true}. هذا يعيد تعيين عدد الفشل.
  3. أرسل اختباراً باستخدام POST …/test. تعني 200 من API الاختبار فقط أنه تم تسجيل المحاولة. تحقق من سجل التسليم وتأكد من outcome == "delivered".
  4. راجع الفجوة. خذ معرفات المهام التي خزنتها أثناء إيقاف نقطة النهاية مؤقتاً واتصل بـ GET /v1/tasks/{id} لكل منها.
  5. فقط بعد ذلك ثق في تدفق خطاف الويب مرة أخرى.

يمنحك سجل التسليم outcome، و http_status، و attempts، و delivered_at. إنه يخزن البيانات الوصفية فقط، لا حمولات. لا يمكن إعادة تشغيل الأحداث القديمة يدوياً، لذا فإن الخطوة 4 ليست اختيارية. معرفات المهام المخزنة لديك هي مسار الاستعادة.

تدوير سر دون إسقاط الأحداث

التدوير غير قابل للعكس، لذا خطط للنافذة قبل أن تبدأ.

  1. اتصل بـ POST /v1/management/webhooks/{webhookId}/rotate-secret. تعيد الاستجابة السر الجديد مرة واحدة.
  2. أضف السر الجديد إلى قائمة التحقق الخاصة بك على جهاز الاستقبال. احتفظ بالقديم في تلك القائمة أيضاً.
  3. انشر تغيير جهاز الاستقبال قبل أن تسقط أي شيء. يجب أن تحتوي القائمة على كلا السرين في وقت واحد.
  4. أرسل اختباراً وتأكد من outcome == "delivered" في السجل.
  5. بعد فترة قصيرة، أزل السر القديم وأعد النشر.

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

إدارة خطافات الويب من MCP

إذا كنت تقود TokenLab من وكيل، فإن خادم MCP يكشف عن نفس دورة الحياة. استخدم @tokenlabai/mcp-server مع ملف تعريف full. الأدوات هي list_webhooks، و create_webhook، و get_webhook، و update_webhook، و delete_webhook، و rotate_webhook_secret، و test_webhook، و list_webhook_deliveries.

يقرأ الخادم Management Token من TOKENLAB_MANAGEMENT_TOKEN. أحدث حزمة منشورة تمت ملاحظتها في 2026-09-28 هي 0.6.24. يعدل MCP نفس نقاط النهاية التي تراها في لوحة التحكم، لذا لا توجد حالة منفصلة للمراجعة.

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

هل ترسل مهام الصور خطافات ويب؟

نعم. كل مهمة غير متزامنة في مساحة العمل، بما في ذلك مهام الصور، ترسل حدث نهايتها إلى نقاط النهاية المشتركة في نوع الحدث ذلك. يخبرك حقل taskType في الحمولة بنوع المهمة التي كانت عليها، على سبيل المثال video أو image. النتائج المتزامنة غير مغطاة.

ماذا يحدث إذا كانت نقطة النهاية الخاصة بي معطلة؟

تعيد كل دورة المحاولة حتى ثلاث مرات. توقف عشر دورات فشل متتالية نقطة النهاية تلقائياً. يمكن إعادة محاولة فشل التسليم المؤقت لاحقاً بنفس معرف التسليم. بمجرد إيقاف نقطة النهاية مؤقتاً، لا يتم تسليم الأحداث من فترة التوقف لاحقاً ولا يمكن إعادة تشغيلها يدوياً. أصلح جهاز الاستقبال، واستأنف نقطة النهاية، وأرسل اختباراً، ثم راجع المهام التي أنشأتها أثناء الفجوة عن طريق الاتصال بـ GET /v1/tasks/{id} باستخدام معرفات المهام المخزنة لديك.

هل يمكنني إعادة تشغيل حدث قديم؟

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

هل task.failed مع retryable: true آمن لإعادة التقديم تلقائياً؟

لا. يصف retryable فشل التوليد. إنه ليس تعليمات لإعادة التقديم. التقديم الجديد هو مهمة جديدة قابلة للفوترة، لذا قرر بشأن إعادة المحاولة بنفسك واحسب التكلفة.

هل يستخدم API توافق Seedance خطافات الويب هذه؟

لا. callback_url لكل طلب هو عقد منفصل بحمولته الخاصة. لا يستخدم أحداث مساحة العمل أو رؤوس HMAC هذه، لذا لا توجه محققاً واحداً لكليهما.

ابدأ بالعقد الكامل في دليل خطاف الويب، ثم أنشئ مفتاح API وقم بتشغيل نقطة النهاية الأولى في مساحة العمل التي تقدم مهامك.

المصادر

النماذج الصادرة حديثًا

ابدأ البناء بالنماذج في هذا الدليل

قارن الأسعار، اختبر المسارات، وحول البحث إلى طلب API يعمل.