跳至主要內容
指南
指南

回呼

安全接收並驗證任務回呼交付。

使用回呼在公開 HTTPS 端點接收 Task 生命週期事件。在處理 JSON 主體之前,請驗證每個回呼,確保只有已為您帳戶簽署的交付內容才能更改應用程式狀態。

配置回呼 URL

建立任務時新增公開的 HTTPS callback_url。事件主體和生命週期狀態取決於端點;請參閱該端點的 API 參考以獲取其回呼負載。

JSON
{
  "model": "flux-2-pro-text-to-image",
  "prompt": "A product photograph on a clean studio background",
  "callback_url": "https://your-domain.com/webhooks/runapi"
}

建立回呼密鑰

請依照身份驗證指南登入, 然後開啟 API 金鑰並為建立 Task 的帳戶建立回呼密鑰。 將該值儲存於密鑰管理器中,且僅向您的回呼接收器開放。它不是 API 金鑰,絕對不得在 Task 請求中發送。

密鑰為該帳戶的回呼簽署。輪替密鑰會更改後續傳遞的簽署,因此請立即更新每個回呼接收器,並僅在足以處理進行中傳遞的時間內保留兩個值。

驗證回呼簽名

每個回呼均為帶有 Content-Type: application/json 的 HTTP POST 請求。 RunAPI 不會在此請求中新增 Authorization 標頭。在反序列化主體之前,請驗證以下標頭:

標頭 含義 X-Callback-Id 此次投遞嘗試的唯一識別碼。 X-Callback-Timestamp 投遞簽署時的 Unix 時間戳記(以秒為單位)。 X-Callback-Signature Base64 編碼的 HMAC-SHA-256 簽章。

使用未修改的請求主體位元組,按照以下方式精確建構簽名值:

TEXT
X-Callback-Id + "." + X-Callback-Timestamp + "." + raw request body

將回呼密鑰進行 Base64 解碼,對該值計算 HMAC-SHA-256,再將結果進行 Base64 編碼,然後使用時序安全比較將其與 X-Callback-Signature 進行比對。在驗證前請勿解析及 重新序列化 JSON。

JavaScript

JAVASCRIPT
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyCallback({headers, rawBody, callbackSecret}) {
  const callbackId = headers["x-callback-id"];
  const timestamp = Number(headers["x-callback-timestamp"]);
  const signature = headers["x-callback-signature"];

  if (!callbackId || !signature || !Number.isSafeInteger(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const signedContent = `${callbackId}.${timestamp}.${rawBody}`;
  const expected = createHmac("sha256", Buffer.from(callbackSecret, "base64"))
    .update(signedContent, "utf8")
    .digest();
  const received = Buffer.from(signature, "base64");

  return expected.length === received.length && timingSafeEqual(expected, received);
}

將框架的原始請求主體字串傳入 rawBody;請勿對已解析的資料呼叫 JSON.stringify

Python

PYTHON
import base64
import hashlib
import hmac
import time


def verify_callback(headers, raw_body, callback_secret):
    callback_id = headers.get("X-Callback-Id")
    timestamp = headers.get("X-Callback-Timestamp")
    signature = headers.get("X-Callback-Signature")

    if not callback_id or not timestamp or not signature:
        return False

    try:
        timestamp = int(timestamp)
        secret = base64.b64decode(callback_secret, validate=True)
        received = base64.b64decode(signature, validate=True)
    except (ValueError, TypeError):
        return False

    if abs(time.time() - timestamp) > 300:
        return False

    signed_content = f"{callback_id}.{timestamp}.{raw_body}".encode("utf-8")
    expected = hmac.new(secret, signed_content, hashlib.sha256).digest()
    return hmac.compare_digest(expected, received)

將 HTTP 框架接收到的原始請求主體字串原封不動地以 raw_body 傳入。

安全處理傳遞

  • 僅在回呼已被接受處理後才返回 2xx 回應。在非 2xx 回應或傳輸失敗後,系統最多會重試投遞 10 次。
  • 請在 15 秒內回應。較慢的工作應在驗證後排隊處理,而非阻塞 HTTP 回應。
  • 拒絕缺少簽名標頭、簽名無效或時間戳超出接收方所定義容差範圍的回呼。

驗證疑難排解

  • 簽名不符: 請確認 Callback Secret 與 Task 屬於同一帳戶,使用 Base64 解碼,並對原始請求主體簽名,而非對解析後的 JSON 簽名。
  • 缺少簽名標頭: 請在依賴回呼進行狀態變更之前先建立 Callback Secret。
  • 時間戳記被拒絕: 請同步接收端的時鐘,並根據您的部署環境使用適當的容差值。

有關設定及生命週期指引,請返回 Task API 快速入門