Skip to content

接收异步图片回调

提交异步图片任务时传入 callback_url,任务进入成功或失败终态后,Revora 会向该地址发送 HTTPS POST 请求。回调只改变结果通知方式,不改变原有模型权限、分组倍率、扣费或消费记录逻辑。

接入步骤

  1. 调用 GET /v1/models 获取当前 Token 可用的图片模型。
  2. 前往 Revora 控制台的“令牌管理”,在对应 Token 的操作菜单中选择“回调密钥”,生成并立即保存 whsec_... 密钥。密钥只显示一次。
  3. 提交 POST /v1/images/generations/async 时传入公网 HTTPS callback_url
  4. 回调服务使用原始请求体和回调密钥验签,按 event_id 防重复处理。
  5. 回调长时间未到时,使用 GET /v1/images/generations/tasks/{task_id} 查询兜底。

密钥轮换

重置回调密钥后,新任务使用新密钥;旧密钥保留 24 小时兼容期,用于完成轮换前已提交任务的回调。接收端可在此期间同时尝试新旧两个密钥,之后删除旧密钥。

提交带回调的任务

bash
curl --request POST 'https://revora.vip/v1/images/generations/async' \
  --header 'Authorization: Bearer YOUR_API_TOKEN' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "model": "YOUR_AVAILABLE_MODEL",
    "prompt": "白色背景上的商品主图,柔和棚拍光线",
    "size": "1024x1024",
    "n": 1,
    "callback_url": "https://merchant.example.com/webhooks/revora-images"
  }'

callback_url 每次任务都可以不同,适合多套测试环境或多业务系统。它必须使用 HTTPS,并且不能指向本机、内网、云元数据地址或需要重定向的地址。Idempotency-Key 仍为可选项;只有客户端可能自动重试、且需要避免重复创建任务时才需要传。

请求头

每次回调包含以下请求头:

请求头说明
X-Revora-Event-Id本次终态事件的唯一 ID,用于幂等去重
X-Revora-Timestamp生成签名时的 Unix 秒级时间戳
X-Revora-Signaturev1= 加十六进制 HMAC-SHA256 摘要
Content-Typeapplication/json

签名原文为:

text
X-Revora-Timestamp + "." + 原始 HTTP 请求体字节

请务必在 JSON 解析前保留原始请求体。重新序列化 JSON 后再验签会导致签名不一致。

成功事件

事件名为 image.generation.succeeded

json
{
  "event_id": "evt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "event": "image.generation.succeeded",
  "created_at": 1784019766,
  "attempt": 1,
  "data": {
    "task_id": "img_task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "status": "succeeded",
    "completed_at": 1784019766,
    "result": {
      "created": 1784019766,
      "data": [
        { "url": "https://image.example.com/result.png" }
      ]
    }
  }
}

结果 URL 是临时签名地址。收到成功事件后请尽快下载并保存到自己的对象存储,不要将该 URL 当作永久地址。

失败事件

事件名为 image.generation.failed

json
{
  "event_id": "evt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "event": "image.generation.failed",
  "created_at": 1784019766,
  "attempt": 1,
  "data": {
    "task_id": "img_task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "status": "failed",
    "completed_at": 1784019766,
    "error": {
      "code": "upstream_error",
      "message": "image generation failed"
    }
  }
}

JavaScript 验签示例

以下示例假设 rawBody 是框架读取到的原始 Buffer,没有经过 JSON 解析或重新编码:

javascript
import crypto from 'node:crypto';

export function verifyRevoraWebhook({ rawBody, headers, secret }) {
  const eventId = headers['x-revora-event-id'];
  const timestamp = headers['x-revora-timestamp'];
  const received = headers['x-revora-signature'];
  if (!eventId || !timestamp || !received?.startsWith('v1=')) return false;

  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
  if (!Number.isFinite(age) || age > 5 * 60) return false;

  const expected = `v1=${crypto
    .createHmac('sha256', secret)
    .update(timestamp)
    .update('.')
    .update(rawBody)
    .digest('hex')}`;

  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

验签成功后,以 X-Revora-Event-Id(或请求体中的 event_id)作为数据库唯一键。业务处理和记录事件 ID 应放在同一事务中。

Python 验签示例

python
import hashlib
import hmac
import time


def verify_revora_webhook(raw_body: bytes, headers, secret: str) -> bool:
    event_id = headers.get("X-Revora-Event-Id")
    timestamp = headers.get("X-Revora-Timestamp")
    received = headers.get("X-Revora-Signature", "")
    if not event_id or not timestamp or not received.startswith("v1="):
        return False

    try:
        if abs(int(time.time()) - int(timestamp)) > 5 * 60:
            return False
    except ValueError:
        return False

    message = timestamp.encode() + b"." + raw_body
    digest = hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(received, "v1=" + digest)

固定验签向量

可使用以下数据验证不同语言的实现是否一致:

text
secret    = whsec_test_secret
timestamp = 1784019766
raw body  = {"event_id":"evt_test","event":"image.generation.succeeded"}
signature = v1=151f3e7ef4371916001b7b68ec1e2abec6db44c4ce5718c0ff23b39c6aec35e5

响应、重试与去重

  • 验签并持久化接收后,请尽快返回任意 HTTP 2xx,建议在 3 秒内返回;耗时的图片下载和业务处理放入自己的队列。
  • 2xx、连接错误或超时会触发重试。最多投递 8 次,间隔依次为:立即、10 秒、1 分钟、5 分钟、30 分钟、2 小时、6 小时、24 小时。
  • 回调采用至少一次投递语义,同一 event_id 可能到达多次。重复事件应直接返回 2xx,不要重复执行订单或素材入库逻辑。
  • 为防止重放攻击,建议拒绝与当前时间相差超过 5 分钟的 X-Revora-Timestamp
  • 回调投递失败不会改变任务终态、扣费或消费记录。可随时使用任务查询接口获取最终结果。

接收端检查清单

  • 只开放 HTTPS,并使用有效公网证书。
  • 对原始请求体执行 HMAC-SHA256 验签。
  • 检查 5 分钟时间窗口。
  • 使用 event_id 唯一索引实现幂等。
  • 先落库再快速返回 2xx,耗时工作异步执行。
  • 保留任务查询接口作为回调丢失时的兜底。

面向 Revora API 下游开发者