跳至正文

Fileverity API 文档

Webhooks

接收 analysis.completed、analysis.failed 和 balance.low 事件。包括 JavaScript 和 Python 中的 Standard Webhooks 签名校验、重试、自动停用和测试事件。

查看 Markdown更新于 2026-11-04

有事件发生时,Webhook 会向您的服务器发送 HTTPS 请求,因此无需轮询。它们遵循 Standard Webhooks 规范。

在控制台的 Webhooks 中添加端点,选择其 URL 和要接收的事件。签名密钥以 whsec_ 开头,仅在创建端点时显示一次。您可以随时修改端点接收的事件,而不会更改其密钥。

如需替换丢失或已泄露的密钥,请在该端点上选择轮换密钥。新密钥只显示一次,旧密钥会立即失效,因此请立即更新您的校验代码。

  • URL 必须使用 HTTPS 和默认端口 443,并指向公网地址。包含凭据或使用其他端口的 URL 会被拒绝。
  • 每个工作区最多可添加 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事件 ID。重试时保持不变;可用它忽略重复投递。
webhook-timestamp本次投递尝试的 Unix 时间(秒)。每次重试都会变化。
webhook-signaturev1, 后接 Base64 编码的 HMAC-SHA256。一个请求头可以包含多个以空格分隔的签名。

校验请求的步骤:

  1. 在进行任何 JSON 解析之前,先将原始请求体读取为文本。
  2. 构造待签名内容:webhook-id、一个英文句点、webhook-timestamp、一个英文句点,再加上原始请求体。
  3. 取出您的签名密钥,去掉 whsec_ 前缀,对其余部分进行 Base64 解码,结果即为 HMAC 密钥。
  4. 对待签名内容计算 HMAC-SHA256,并进行 Base64 编码。
  5. 使用恒定时间比较,将其与请求头中的每个 v1 签名进行比对。
  6. 拒绝与您的系统时钟相差超过五分钟的时间戳。
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);  }});

请针对实际发送的原始字节进行校验。先解析再重新序列化 JSON 会改变这些字节,导致签名校验失败。

投递与重试

  • 请在 10 秒内返回任意 2xx 状态码。不会跟随重定向,重定向视为失败。
  • 其他任何情况,包括超时或连接错误,都会安排重试,间隔逐步增大:5、10、20、40、80、160、320 和 640 分钟,之后每 12 小时一次。事件发生 3 天后停止重试。重试按计划调度,因此实际重试可能晚于既定间隔,但绝不会提前。
  • 投递至少发生一次,且不保证顺序。请使用 webhook-id 忽略重复投递;如需最新状态,请获取该分析。
  • 控制台会列出最近 50 次投递及其状态和错误,保留 30 天,并支持重新发送某一次投递。打开某次投递,可以查看其请求内容、响应时间以及您的端点所返回响应的前 2 KB,因此请勿在该响应中返回密钥或个人数据。重新发送时沿用原来的事件 ID。
  • 请求的 User-Agent 为 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  }}

相关内容:分析、幂等性和错误。