# コールバック
> タスクの終了通知：署名検証、ヘッダー、リトライポリシー、payload の構造。

タスク作成時に `callback_url`（HTTPS 必須）を渡すと、タスクが終了状態に達した後に Beatra から POST で通知が届きます。コールバックとポーリングは排他ではありません。タスクの状態は常に `GET /v1/tasks/{task_id}` が正です。

## 受け取る内容

`POST {callback_url}` が届きます。**10 秒以内に 2xx を返す**と配信成功とみなされます。

| Header | 説明 |
| --- | --- |
| `X-Event-Id` | イベントの一意 ID。リトライ間で不変 —— **これをキーに冪等な重複排除を行ってください** |
| `X-Delivery-Id` | 配信試行ごとに一意 |
| `X-Delivery-Attempt` | 何回目の試行か（1 始まり） |
| `X-Task-Id` | タスク ID |
| `X-Event-Type` | `task.succeeded` / `task.failed` / `task.canceled` |
| `X-Timestamp` | Unix 秒タイムスタンプ |
| `X-Signature-Key-Id` | 選択した署名キー ID。署名付き配信の場合のみ存在 |
| `X-Signature` | 署名。署名付き配信の場合のみ存在。下記参照 |

```json
{
  "event_id": "evt_01JX...",
  "event_type": "task.succeeded",
  "event_time": "2026-06-11T10:30:00.123456Z",
  "task": { "task_id": "task_01JX...", "status": "succeeded", "output": { "...": "..." } }
}
```

`task` は完全な [Task envelope](https://docs.beatra.ai/ja/docs/mechanisms/async-tasks) です。

## 署名付きまたは署名なしの配信を選ぶ

コールバック署名キーは API キーとは完全に独立しています。
**Console → Developer → Webhooks** または
`/account/callback-signing-keys` エンドポイントで作成、ローテーション、
無効化できます。平文のシークレットは作成またはローテーション時に一度だけ表示されます。

- `callback_url` だけを渡すと署名なしで配信され、2 つの署名ヘッダーは付きません。
- `callback_url` と有効な `callback_signing_key_id` を渡すと署名付きで配信されます。
- 選択したキーを利用できない場合は配信に失敗し、署名なしへ暗黙に切り替わることはありません。

## 署名付き配信を検証する

```text
X-Signature: t=<unix_ts>,v1=<hex>
v1 = HMAC-SHA256( secret, "<unix_ts>." + raw_body )
```

```python
import hmac, hashlib, time

def verify(signature_header: str, raw_body: bytes, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    ts, sig = int(parts["t"]), parts["v1"]
    if abs(time.time() - ts) > 300:          # 5 分以上ずれたタイムスタンプは拒否
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body,
                        hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)
```

注意：必ず**生のリクエストボディのバイト列**に対して計算してください。先にパースしてから再シリアライズすると検証に失敗します。

## リトライポリシー

配信に失敗（非 2xx またはタイムアウト）すると、以下の間隔でリトライします。配信は合計 6 回（初回 1 + リトライ 5）です：

```text
1 分 → 5 分 → 30 分 → 2 時間 → 12 時間
```

- リトライ中も `X-Event-Id` は変わりません。これをキーに重複排除してください
- 配信の失敗は**タスク自体には影響しません**——タスクはすでに終了状態にあり、いつでもポーリングでフォールバックできます
- 各配信の状態は、envelope の `callback` フィールド（`pending / dispatching / delivered / retrying / failed`、`attempt_count`、`last_error`）とコンソールのコールバックログで確認できます
