コンテンツへスキップ
ガイド
ガイド

コールバック

タスクコールバックの配信を安全に受信して検証します。

公開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キーを開いてタスクを作成するアカウントのCallback Secretを作成します。値をシークレットマネージャーに保存し、コールバックレシーバーのみが利用できるようにしてください。これは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 がタスクと同じアカウントに属していることを確認し、Base64 でデコードし、解析された JSON ではなく元のボディで署名してください。
  • 署名ヘッダーがない: 状態変化のコールバックに依存する前に Callback Secret を作成してください。
  • タイムスタンプが拒否される: 受信側のクロックを同期し、デプロイ環境に適した許容範囲を使用してください。

セットアップとライフサイクルのガイダンスについては、Task API クイックスタートに戻ってください。