> ## Documentation Index
> Fetch the complete documentation index at: https://docs.toapis.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 非同期タスク Webhook

> ToAPIs の署名済み完了・失敗イベント

> [!NOTE]
>
> **中国本土のユーザー向け：** 中国本土のユーザーは `https://toapis.cn` を API エンドポイント（Base URL）としてご利用ください。本ドキュメント内の例では `https://toapis.com` を `https://toapis.cn` に置き換えてください。

非同期の画像と動画に `generation.completed` / `generation.failed`、テストに `endpoint.test` を送信します。at-least-once 配信のため、同じ event ID を冪等に処理してください。

Token 編集画面で HTTPS の既定 URL、1回だけ表示される32-byte秘密鍵、Webhook 有効化、テストを設定します。`callback_url` 未指定時は既定 URL を使用し、指定時は同一 scheme/host/port の path/query のみ変更できます。未設定なら `400 webhook_not_configured` です。

```json theme={null}
{"id":"evt_xxx","type":"generation.completed","api_version":"v1","created_at":1783953302,"data":{"task_id":"tsk_xxx","client_business_id":"order_123","task_type":"image","model":"model-name","status":"SUCCESS","progress":"100%","result":{"type":"image","data":[{"url":"https://cdn.example/result.png"}]},"usage":{"charge_type":"per_request","requests":1},"billing":{"status":"settled","credits":"100","cost_usd":"0.5"},"error":null,"created_at":1783953000,"finished_at":1783953300,"completed_at":1783953300,"expires_at":1784039700}}
```

最終失敗は内部 retry と返金または課金確定の完了後にだけ送信します。

識別子と対応は[エラー早見表](https://docs.toapis.com/docs/ja/api-reference/errors.md)を参照してください. Webhook の `data.error` は, 一部のタスク照会にある `reason` を保証しません.

```json theme={null}
{"id":"evt_failure","type":"generation.failed","api_version":"v1","created_at":1783953302,"data":{"task_id":"tsk_failure","client_business_id":"order_456","task_type":"video","model":"model-name","status":"FAILURE","progress":"100%","result":null,"billing":{"status":"refunded","credits":"0","cost_usd":"0"},"error":{"code":"generation_failed","message":"Generation failed after retries"},"created_at":1783953000,"finished_at":1783953300}}
```

```json theme={null}
{"id":"evt_test","type":"endpoint.test","api_version":"v1","created_at":1783953302,"data":{"task_id":"endpoint_test","task_type":"image","status":"SUCCESS","progress":"100%","result":{"message":"ToAPIs webhook endpoint test"},"error":null,"created_at":0}}
```

`task_type` は `image|video`。結果は公開タスク照会と同じ形式で、channel ID、upstream task ID/key、認証情報、PrivateData は含みません。

## 使用量, 課金とタスク照会の違い

画像と動画の成功イベントは `data.usage` を含む場合があります. 課金数量, 取得できたモデルのトークン内訳, ツール使用回数の定義はタスク照会と同じです. [タスクの使用量フィールド](https://docs.toapis.com/docs/ja/api-reference/account/pricing.md)を参照してください. 動画イベントは `data.generation` を含む場合もあります. Seedance のメタデータと互換フィールドは[動画タスク照会](https://docs.toapis.com/docs/ja/api-reference/tasks/video-status.md)を参照してください.

`data.billing` は `status`, `credits`, `cost_usd` を含む場合があります. 金額は十進数の文字列で, `pending` では省略します. `refunded` は確認済みの正味請求額がゼロであることを示します. 確認できないオブジェクトは省略しますが, 無料とは限りません. Webhook とタスク照会は同じ確認条件を使います. 送信時に一括で課金される固定料金タスクを含め, 課金が確認済みであれば両方とも `settled` と実際の金額を返します. `data.usage` に `charge_type` などの課金数量が含まれる場合, `data.billing` も必ず `settled` です.

### `data.billing` のフィールド値

| フィールド | 型 | 説明 |
| - | - | - |
| `status` | string | `pending`: 確定または返金が未確認で, 金額は省略されます. `settled`: 最終請求額が確定済みで, 実際の金額を返します. 無料タスクは `"0"` を返します. `refunded`: 返金済み, または失敗後に正味請求額なしと確認済みで, 両方の金額は `"0"` です |
| `credits` | string | 実際に請求された ToAPIs Credits. `"100"` のような 10 進文字列です. `settled` と `refunded` の場合のみ返されます |
| `cost_usd` | string | 同じ請求額の USD 金額. `"0.5"` のような 10 進文字列です. `credits` と同じ条件で返され, 割引と倍率を含み, プロバイダー原価ではありません |

成功イベントは `settled` で, 確定時に予約額が全額戻る場合は `refunded` です. 失敗イベントは返金完了後に送信され, `refunded` です. 金額を確認できない場合は, `data.billing` オブジェクト全体を省略します.

### `data.usage` のフィールド値

| フィールド | 型 | 説明 |
| - | - | - |
| `charge_type` | string | 課金方式: `per_request`, `per_second`, `per_token` |
| `requests` | integer | 課金対象のリクエスト数. `per_request` の場合に返されます. 画像リクエストの `n` は直接乗算されません |
| `billed_tokens` | integer | 料金計算に使用したトークン数. `per_token` の場合に返されます |
| `output_seconds` | integer | 課金対象となる出力動画の長さ, 整数秒. `per_second` の場合に返されます |
| `input_seconds` | integer | 課金対象となる入力動画の長さ, 整数秒. ゼロの場合は省略します |
| `input_images` | integer | 無料枠を差し引いた課金対象の入力画像数. ゼロの場合は省略します |
| `input_tokens`, `output_tokens`, `total_tokens` | integer | モデルが報告した入力, 出力, 合計のトークン数 |
| `input_tokens_details` | object | 画像入力の内訳: `text_tokens`, `image_tokens`, `cached_tokens`, `cached_tokens_details.text_tokens`, `cached_tokens_details.image_tokens`. キャッシュ分は入力合計にすでに含まれます |
| `output_tokens_details` | object | 画像出力の内訳: `text_tokens`, `image_tokens`. テキストと思考が同価格で細分化されていない場合は `text_or_reasoning_tokens` が追加され, `text_tokens` には含まれません |
| `prompt_tokens`, `completion_tokens` | integer | 動画モデルが報告したトークン数 |
| `tool_usage` | object | ツール使用回数: `web_search`, `image_search`, `grounding_queries`. 使用した場合に返されます |

`charge_type` から `input_images` までは課金数量であり, `data.billing.status=settled` と同時にのみ返されます. トークン内訳とツール使用回数は, 上流モデルが提供したとおりに独立して返されます. 返されないフィールドは省略され, ゼロで補完されません.

Webhook はイベント作成時のスナップショットで, タスク照会は最新の確認済み状態を読み取ります. 使用量や金額の補完を待たずに配信し, 再試行ルールも変わりません. 作成済みのイベントは補完されず, 再試行では元のイベント内容を使用します.

イベント構造と `api_version: "v1"` は変わりません. Webhook は `data.task_id`, `SUCCESS` / `FAILURE`, 文字列の進行率 `"100%"` を使います. タスク照会は `id`, 小文字の状態, 整数の進行率を使います. 時刻フィールドは公開 `generation.task` 照会と同じ名前と意味で, すべて Unix 秒です. `data.created_at` はタスクの送信時刻, `data.finished_at` は終了時刻で, 成功時は `data.completed_at` と `data.expires_at` (結果の有効期限, 終了時刻の 24 時間後) も返します. 値のないフィールドは省略します. 従来の `data.submitted_at` は `data.created_at` に置き換えられ, 返されなくなりました. イベントのトップレベル `created_at` はイベント作成時刻で, タスクの送信時刻ではありません. アクセスポリシーによってメディア URL が異なる場合があります.

### Seedance 動画の成功イベント例

サンプルはフィールドを示すもので, モデルの現在の価格や固定の課金方式を示すものではありません. 実際のタスクで返されたフィールドと金額を確認してください.

Seedance の成功イベントでは `data.result.data[]` に `fallback_url` と `fallback_last_frame_url` が含まれることもあります. `url` と `last_frame_url` を優先し, 元の URL にアクセスできない場合のみ対応する代替 URL を使用してください. 代替フィールドの省略はタスク失敗を意味しません. イベントは作成時の URL を保持し, その後コピーが変わっても既存イベントや再試行のペイロードは変更されません. 定義と使用方法は[動画タスクの照会](https://docs.toapis.com/docs/ja/api-reference/tasks/video-status.md)を参照してください.

```json theme={null}
{
  "id": "evt_seedance_example",
  "type": "generation.completed",
  "api_version": "v1",
  "created_at": 1792400061,
  "data": {
    "task_id": "task_seedance_example",
    "client_business_id": "order_123",
    "task_type": "video",
    "model": "seedance-2-global",
    "status": "SUCCESS",
    "progress": "100%",
    "result": {
      "type": "video",
      "data": [
        {
          "url": "https://cdn.example/result.mp4",
          "fallback_url": "https://files.toapis.com/videos/task_seedance_example/result.mp4",
          "format": "mp4",
          "last_frame_url": "https://cdn.example/last-frame.jpg",
          "fallback_last_frame_url": "https://files.toapis.com/videos/task_seedance_example/last-frame.jpg"
        }
      ]
    },
    "usage": {
      "charge_type": "per_token",
      "billed_tokens": 87300,
      "completion_tokens": 87300,
      "total_tokens": 87300
    },
    "billing": {
      "status": "settled",
      "credits": "87.3",
      "cost_usd": "0.4365"
    },
    "generation": {
      "duration": 5,
      "resolution": "720p",
      "ratio": "16:9",
      "generate_audio": true,
      "usage": {
        "completion_tokens": 87300,
        "total_tokens": 87300
      }
    },
    "error": null,
    "created_at": 1792400000,
    "finished_at": 1792400060,
    "completed_at": 1792400060,
    "expires_at": 1792486460
  }
}
```

```text theme={null}
User-Agent: ToAPIs-Webhook/1.0
X-ToAPIs-Webhook-Id
X-ToAPIs-Webhook-Timestamp
X-ToAPIs-Webhook-Signature: v1=<current>[,v1=<previous>]
```

HMAC-SHA256 の入力は `event_id + "." + timestamp + "." + raw_request_body`。初回は5分を超える時刻差を拒否し、ローテーション後24時間は新旧署名のどちらかを検証します。

```python Python theme={null}
def verify(headers, raw_body, secrets):
    event_id=headers["X-ToAPIs-Webhook-Id"]; timestamp=int(headers["X-ToAPIs-Webhook-Timestamp"])
    if abs(int(time.time())-timestamp)>300: return False
    signed=f"{event_id}.{timestamp}.".encode()+raw_body
    received=[v.strip()[3:] for v in headers["X-ToAPIs-Webhook-Signature"].split(",") if v.strip().startswith("v1=")]
    return any(hmac.compare_digest(hmac.new(s.encode(),signed,hashlib.sha256).hexdigest(),r) for s in secrets for r in received)
```

```javascript Node.js theme={null}
function verify(headers,rawBody,secrets){const id=headers['x-toapis-webhook-id'];const ts=Number(headers['x-toapis-webhook-timestamp']);if(!Number.isFinite(ts)||Math.abs(Date.now()/1000-ts)>300)return false;const signed=Buffer.concat([Buffer.from(`${id}.${ts}.`),rawBody]);const received=headers['x-toapis-webhook-signature'].split(',').filter(v=>v.trim().startsWith('v1=')).map(v=>v.trim().slice(3));return secrets.some(secret=>received.some(sig=>{const expected=crypto.createHmac('sha256',secret).update(signed).digest('hex');return expected.length===sig.length&&crypto.timingSafeEqual(Buffer.from(expected),Buffer.from(sig));}));}
```

```go Go theme={null}
func Verify(id,ts,sig string,body []byte,secrets []string) bool { unix,err:=strconv.ParseInt(ts,10,64);now:=time.Now().Unix();if err!=nil||now-unix>300||unix-now>300{return false};signed:=append([]byte(id+"."+ts+"."),body...);for _,secret:=range secrets{mac:=hmac.New(sha256.New,[]byte(secret));mac.Write(signed);expected:=hex.EncodeToString(mac.Sum(nil));for _,v:=range strings.Split(sig,","){if hmac.Equal([]byte(expected),[]byte(strings.TrimPrefix(strings.TrimSpace(v),"v1="))){return true}}};return false }
```

初回は即時、失敗後は10秒、30秒、2分、10分、1時間、6時間、24時間で再試行します。成功は `2xx` のみで、`3xx` は追従せず、`4xx/5xx` は再試行します。timeout は10秒です。HTTPS、userinfo、fragment、不正 port、SSRF、DNS/IP、DNS rebinding を配信ごとに検査します。失敗時はタスク照会をフォールバックにし、期限付き result URL は早めに保存してください。本人認証、決済、OAuth callback は対象外です。[レート制限](https://docs.toapis.com/docs/ja/api-reference/rate-limits/async-tasks.md)。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.