توثيق واجهة Fileverity API
Webhooks
استقبل أحداث analysis.completed وanalysis.failed وbalance.low. التحقق من توقيع Standard Webhooks بلغتي JavaScript وPython، وإعادة المحاولة، والتعطيل التلقائي، وحدث الاختبار.
ترسل Webhooks طلب HTTPS إلى خادمك عند وقوع حدث ما، فلا تحتاج إلى الاستعلام المتكرر. وهي تتبع مواصفة Standard Webhooks.
أضف نقاط النهاية في لوحة التحكم ضمن Webhooks. اختر عنوان URL والأحداث التي تستقبلها. يظهر المفتاح السري للتوقيع، الذي يبدأ بالبادئة whsec_، مرة واحدة عند إنشاء نقطة النهاية. ويمكنك تغيير أحداث نقطة النهاية في أي وقت دون تغيير مفتاحها السري.
لاستبدال مفتاح سري ضائع أو مُسرَّب، اختر جدّد المفتاح السري على نقطة النهاية. يظهر المفتاح الجديد مرة واحدة ويتوقف القديم عن العمل فورًا، لذا حدّث شيفرة التحقق لديك في الحال.
- يجب أن يستخدم عنوان URL بروتوكول HTTPS على المنفذ الافتراضي 443 وأن يشير إلى عنوان عام. تُرفض العناوين التي تتضمن بيانات اعتماد أو التي تستخدم منفذًا آخر.
- يمكن أن تضم مساحة العمل حتى 10 نقاط نهاية.
- لا ترسل مفاتيح الاختبار أي أحداث أبدًا.
الأحداث
| الحدث | متى يُرسَل |
|---|---|
analysis.completed | نجح تحليل صورة أو مستند. |
analysis.failed | فشل تحليل أو أُلغي. لم تُحتسب عليه رسوم. ويحمل التحليل الملغى status: canceled وerror: canceled. |
balance.low | انخفض الرصيد عن حد التنبيه الذي حدّدته في لوحة التحكم. |
أنواع الأحداث المدعومة هي analysis.completed وanalysis.failed وbalance.low.
محتوى الطلب
لكل حدث البنية الخارجية نفسها: id وobject (قيمته دائمًا event) وtype وcreated (طابع زمني بصيغة Unix) وdata. ويُرسَل id أيضًا في الترويسة webhook-id.
analysis.completed وanalysis.failed
data هو كائن التحليل نفسه الذي يعيده GET /analyses/{id}، بأسماء الحقول وأنواعها نفسها، باستثناء أن result يكون دائمًا null؛ اجلب النتيجة من نقطة النهاية تلك. قيمة status هي succeeded أو failed أو canceled، أما error فهو كائن يضم code وmessage، أو null. الطوابع الزمنية بصيغة Unix بالثواني.
{ "id": "evt_Lm8sK3tR6yZ2cXaQ9vB4nP1w", "object": "event", "type": "analysis.completed", "created": 1793700131, "data": { "id": "an_8Hk2pQ4vN9sL1xR7cW3mY6zB", "object": "analysis", "created": 1793700120, "model": "fvimg-2-2026-10-30", "livemode": true, "status": "succeeded", "file": { "name": "claim-photo.jpg", "bytes": 2481920, "format": "jpeg" }, "report_id": "FV-7K2M-9QXD-4T8R", "report_url": "https://fileverity.com/verify/FV-7K2M-9QXD-4T8R", "summary": { "level": "L2", "assessment": "authentic_untouched", "action": "accept", "ai": "very_low", "edit": "none" }, "result": null, "error": null, "progress": null, "usage": { "amount": 199, "currency": "usd", "funding": "balance" }, "metadata": { "order_id": "A-10492" }, "completed_at": 1793700131, "expires_at": 1794304931 }}{ "id": "evt_Zc4vB7nP1sK8tL3wR6yQm2Xa", "object": "event", "type": "analysis.failed", "created": 1793700131, "data": { "id": "an_8Hk2pQ4vN9sL1xR7cW3mY6zB", "object": "analysis", "created": 1793700120, "model": "fvimg-2-2026-10-30", "livemode": true, "status": "failed", "file": { "name": "claim-photo.jpg", "bytes": 2481920, "format": "jpeg" }, "report_id": null, "report_url": null, "summary": null, "result": null, "error": { "code": "analysis_failed", "message": "The analysis could not be completed. You were not charged; retry it, and contact support with the request ID if it fails again." }, "progress": null, "usage": { "amount": 0, "currency": "usd", "funding": null }, "metadata": null, "completed_at": 1793700135, "expires_at": null }}balance.low
balance وthreshold بسنتات الدولار الأمريكي.
{ "id": "evt_Lm8sK3tR6yZ2cXaQ9vB4nP1w", "object": "event", "type": "balance.low", "created": 1793700500, "data": { "balance": 640, "threshold": 1000 }}التحقق من التوقيع
يحمل كل طلب ثلاث ترويسات، كما تحددها مواصفة Standard Webhooks:
| الترويسة | القيمة |
|---|---|
webhook-id | معرّف الحدث. وهو نفسه في كل إعادة محاولة؛ استخدمه لتجاهل التكرارات. |
webhook-timestamp | وقت Unix بالثواني لمحاولة التسليم هذه. ويتغير مع كل إعادة محاولة. |
webhook-signature | v1, متبوعة بقيمة HMAC-SHA256 بترميز base64. يمكن أن تحمل الترويسة عدة توقيعات تفصل بينها مسافات. |
للتحقق من طلب:
- اقرأ محتوى الطلب الخام بوصفه سلسلة نصية، قبل أي تحليل لـ JSON.
- كوّن المحتوى الموقَّع:
webhook-idثم نقطة ثمwebhook-timestampثم نقطة ثم محتوى الطلب الخام. - خذ المفتاح السري للتوقيع، وأزل البادئة
whsec_، ثم فكّ ترميز base64 للباقي. هذا هو مفتاح HMAC. - احسب HMAC-SHA256 للمحتوى الموقَّع ثم رمّزه بصيغة base64.
- قارنه، في زمن ثابت، بكل توقيع
v1في الترويسة. - ارفض الطوابع الزمنية التي تبعد أكثر من خمس دقائق عن ساعة خادمك.
import { createHmac, timingSafeEqual } from "node:crypto"; export function verify(secret, headers, body) { const id = headers["webhook-id"]; const timestamp = headers["webhook-timestamp"]; const signatures = headers["webhook-signature"]; if (!id || !timestamp || !signatures) throw new Error("Missing webhook headers"); if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) throw new Error("Timestamp outside tolerance"); const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const expected = createHmac("sha256", key).update(id + "." + timestamp + "." + body).digest(); const valid = signatures.split(" ").some((entry) => { const [version, value] = entry.split(","); const given = Buffer.from(value ?? "", "base64"); return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected); }); if (!valid) throw new Error("Invalid signature"); return JSON.parse(body);} app.post("/webhooks/fileverity", express.raw({ type: "application/json" }), (req, res) => { try { const event = verify(process.env.FILEVERITY_WEBHOOK_SECRET, req.headers, req.body.toString("utf8")); handle(event); res.sendStatus(200); } catch { res.sendStatus(400); }});import base64import binasciiimport hashlibimport hmacimport jsonimport osimport time from fastapi import FastAPI, HTTPException, Request app = FastAPI() def verify(secret, headers, body): message_id = headers["webhook-id"] timestamp = headers["webhook-timestamp"] if abs(time.time() - int(timestamp)) > 300: raise ValueError("Timestamp outside tolerance") key = base64.b64decode(secret.removeprefix("whsec_")) signed = (message_id + "." + timestamp + ".").encode() + body expected = hmac.new(key, signed, hashlib.sha256).digest() for entry in headers["webhook-signature"].split(" "): version, _, value = entry.partition(",") try: given = base64.b64decode(value, validate=True) except binascii.Error: continue if version == "v1" and hmac.compare_digest(given, expected): return json.loads(body) raise ValueError("Invalid signature") @app.post("/webhooks/fileverity")async def receive(request: Request): body = await request.body() try: event = verify(os.environ["FILEVERITY_WEBHOOK_SECRET"], request.headers, body) except (KeyError, ValueError): raise HTTPException(status_code=400) handle(event) return {"ok": True}تحقق من البايتات نفسها تمامًا التي أُرسلت. فتحليل JSON ثم إعادة تسلسله يغيّرانها ويُبطلان التوقيع.
التسليم وإعادة المحاولة
- استجب بأي حالة
2xxخلال 10 ثوانٍ. لا تُتّبع عمليات إعادة التوجيه وتُعدّ فشلًا. - أي استجابة أخرى، بما فيها انتهاء المهلة أو خطأ الاتصال، تجدول إعادة محاولة بفواصل زمنية متزايدة: 5 و10 و20 و40 و80 و160 و320 و640 دقيقة، ثم كل 12 ساعة. وتتوقف إعادة المحاولة بعد 3 أيام من وقوع الحدث. وتعمل إعادة المحاولة وفق جدول زمني، فقد تأتي بعد موعدها، لكنها لا تأتي قبله أبدًا.
- يجري التسليم مرة واحدة على الأقل، ولا يُضمن الترتيب. استخدم
webhook-idلتجاهل التكرارات، واجلب التحليل إذا احتجت إلى أحدث حالة له. - تعرض لوحة التحكم آخر 50 عملية تسليم مع حالتها وأخطائها، وتحتفظ بها 30 يومًا، وتتيح لك إعادة إرسال أي منها. افتح عملية تسليم لترى محتوى الطلب وزمن الاستجابة وأول 2 كيلوبايت من استجابة نقطة النهاية لديك، لذا لا تُرجع أسرارًا أو بيانات شخصية في تلك الاستجابة. وتحتفظ إعادة الإرسال بمعرّف الحدث الأصلي.
- تحمل الطلبات وكيل المستخدم
Fileverity-Webhooks/1.0.
التعطيل التلقائي
تُعطَّل نقطة النهاية التي تفشل باستمرار مدة 3 أيام. ولا تستقبل بعد ذلك أي أحداث جديدة، وتُوسَم أحداثها التي لم تُسلَّم بأنها فاشلة. وأي عملية تسليم ناجحة قبل ذلك تُزيل حالة الفشل. يُرسَل بريد إلكتروني إلى مالكي مساحة العمل ومسؤوليها عند تعطيل نقطة نهاية. أعد تفعيل نقطة النهاية في لوحة التحكم بعد إصلاح المشكلة.
حدث الاختبار
استخدم أرسل اختبارًا على نقطة نهاية في لوحة التحكم لتسليم حدث تجريبي فورًا. وهو حدث analysis.completed موقَّع تكون فيه قيمة data.test هي true ولا يتضمن تقريرًا، فيمكنك التحقق من عنوان URL وشيفرة التوقيع قبل وصول الأحداث الحقيقية.
{ "id": "evt_Qm4vB7nP1sK8tL3wR6yZ2cXa", "object": "event", "type": "analysis.completed", "created": 1793700500, "data": { "id": "an_Pw3mY6zB8Hk2pQ4vN9sL1xR7", "object": "analysis", "created": 1793700120, "model": "fvimg-2-2026-10-30", "livemode": true, "status": "succeeded", "file": { "name": "claim-photo.jpg", "bytes": 2481920, "format": "jpeg" }, "report_id": null, "report_url": null, "summary": null, "result": null, "error": null, "progress": null, "usage": { "amount": 199, "currency": "usd", "funding": "balance" }, "metadata": { "order_id": "A-10492" }, "completed_at": 1793700131, "expires_at": 1794304931, "test": true }}ذات صلة: التحليلات وIdempotency والأخطاء.