---
title: "Webhook の概要"
description: "ThunderPhone がリアルタイムイベントを配信する方法、署名を検証する方法、従来の配信モデルとエンドポイントベースの配信モデルの比較を説明します。"
---

ThunderPhoneは、通話中にイベントが発生するとサーバーへ HTTP `POST` リクエストを送信します。たとえば、着信通話の開始、通話の終了、評価実行の完了、アラートの発火などです。配信モデルは **2種類あります**。

<CardGroup cols={2}>
  <Card title="Webhookエンドポイント（推奨）" icon="bolt" href="/ja/webhooks/endpoints">
    複数の URL、エンドポイントごとのシークレット、エンドポイントごとのイベントフィルター、
    および自動再試行を提供します。
    `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints` で管理します。
  </Card>
  <Card title="単一URLのレガシーWebhook" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    組織ごとに1つの URL。**ブロッキング**構成の交換を含む、
    通話ライフサイクルイベントを配信します。`GET/PUT /v1/webhook` で管理します。
  </Card>
</CardGroup>

[イベントカタログ](/ja/webhooks/events)に記載されているすべてのイベントタイプは、
Webhookエンドポイントを通じて配信されます。6つの通話ライフサイクルイベント
（`telephony.incoming`、`telephony.complete`、`telephony.tool`、
`web.incoming`、`web.complete`、`web.tool`）は、レガシーの単一URL
Webhookにも**送信されます**。レガシー URL と一致するエンドポイントの両方がある場合、
イベントは**両方の**パスで受信します。ブロッキング動作（[`telephony.incoming` / `web.incoming` の構成交換](/ja/webhooks/call-incoming)およびWebhookモードの
[ツールディスパッチ](/ja/tools/overview)）はレガシーパス専用です。
すべてのエンドポイント配信はファイアアンドフォーゲット通知です。

## ペイロード形式

エンドポイント配信は、`data`、`event_id`、`type` を含む JSON オブジェクトです。

```json
{
  "data": {
    "call_id": 987654321,
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  },
  "event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
  "type": "telephony.incoming"
}
```

`event_id` は送信されるイベントごとに一意です。再試行時**および**イベントを受信するすべてのエンドポイントで同一です。この値で重複除外してください。

レガシーの単一URL Webhookは同じ `type` と `data` を送信しますが、
`event_id` **は含まれません**。

```json
{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}
```

通信上では、すべての本文が正規形式でシリアル化されます。キーはアルファベット順に並び、
空白は含まれず、UTF-8 を使用します。このドキュメントの整形済みの例は、読みやすさのみを目的としています。

イベントタイプとペイロードフィールドの完全な一覧は、[イベントカタログ](/ja/webhooks/events)を参照してください。

## 署名の検証

各リクエストには 2 つの署名が含まれます。いずれもエンドポイントの `secret`（レガシー配信では組織レベルの webhook `secret`）をキーとする HMAC-SHA256 です。

- `X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<hex>` は、タイムスタンプ、メソッド、パスとクエリ、生の本文に署名します。こちらを検証し、取得されたリクエストが後で再送されないよう、`t` がサーバー時刻から **5 分**を超えてずれているリクエストは拒否してください。再試行は送信時に再度署名されます。
- `X-ThunderPhone-Signature` は生の本文のみに署名します。既存の検証処理が引き続き機能するよう、変更せずに送信されます。

### 手順

1. ヘッダー、メソッド、生のパスとクエリ、および生の本文を、**解析前に**読み取ります。
2. シークレットを使用して、`<t>\n<METHOD>\n<path and query>\n<body>` の16進数HMAC-SHA256を計算します。
3. `t` が300秒を超えてずれている場合、または定数時間比較で結果が `v1` と一致しない場合は、リクエストを拒否します。

本文は正規JSONシリアル化（キーをソートし、区切り文字をコンパクト化）であり、送信するバイト列をそのまま署名します。[検証ガイド](/ja/guides/verify-webhook-signatures)には、正確な署名対象文字列、実例、SDKを使用しないコード、レガシーヘッダーが記載されています。

<CodeGroup>
```python Python (Flask)
from flask import Flask, abort, request
from thunderphone import verify_signature

app = Flask(__name__)

@app.post("/thunderphone-webhook")
def handle():
    query = request.query_string.decode()
    if not verify_signature(
        request.headers.get("X-ThunderPhone-Signature-V2"),
        secret=WEBHOOK_SECRET,
        method=request.method,
        path=request.path + (f"?{query}" if query else ""),
        body=request.get_data(),  # raw bytes
    ):
        abort(401)
    event = request.get_json()
    # dispatch on event["type"] …
    return "", 204
```

```javascript Node.js (Express)
import express from "express";
import { verifySignature } from "@thunderphone/sdk";

const app = express();
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const ok = verifySignature({
      header: req.header("X-ThunderPhone-Signature-V2"),
      secret: process.env.WEBHOOK_SECRET,
      method: req.method,
      path: req.originalUrl,
      body: req.body,
    });
    if (!ok) return res.sendStatus(401);
    const event = JSON.parse(req.body.toString("utf8"));
    // dispatch on event.type …
    res.sendStatus(204);
  },
);
```
</CodeGroup>

## 配信セマンティクス

これらのセマンティクスは、**エンドポイント**配信に適用されます。従来の単一 URL
Webhook は再試行なしの単一同期試行ですが、電話通話の
[`telephony.incoming`](/ja/webhooks/call-incoming)
設定交換のみ、1 回再試行されます。

<AccordionGroup>
  <Accordion title="再試行">
    各イベントは直ちに 1 回試行されます。`2xx` 応答はすべて、
    配信を確認します。その他の結果（非 2xx、
    接続エラー、タイムアウト）の場合は、**最初の試行から 1 m、5 m、30 m、2 h、6 h、
    12 h、24 h 後**に再試行します。つまり 24 時間にわたり
    8 回試行します。すべての試行が失敗すると配信は停止し、エンドポイントは
    [Webhook エンドポイント](/ja/webhooks/endpoints)で
    `status="failing"` としてマークされます。組織の
    オーナーと管理者にメールで通知し、ダッシュボードにも表示します。ペイロードが
    永続的に受け入れられたら、できるだけ早く `2xx` を返してください。
    処理は非同期で行ってください。
    エンドポイントテスト API からのテスト送信は、最初の試行から約 1 分後に
    1 回再試行されますが、エンドポイントのステータスは変更されません。
  </Accordion>

  <Accordion title="順序">
    配信順序はベストエフォートです。実際にはイベントが発行された順序で配信しますが、
    失敗時の再試行によって順序が変わることがあります。
    常に `call_id` / オブジェクト ID で重複を排除し、整合性を確認してください。
  </Accordion>

  <Accordion title="重複">
    配信は**少なくとも 1 回**です。確認できなかった応答後の再試行により、
    イベントが重複する可能性があります。すべての再試行には同じ
    `event_id` が含まれるため、処理済み ID を保存して重複をスキップしてください。`event_id` は
    エンドポイント間でも共有されます。同じイベントを購読している 2 つのエンドポイントは、
    同じ `event_id` を受け取ります。
  </Accordion>

  <Accordion title="タイムアウト">
    エンドポイント配信では、各試行のタイムアウトは **30 s** です。従来の
    パスでは、リアルタイムの通話動作を制御するブロッキングリクエスト、つまり
    [`telephony.incoming` / `web.incoming`](/ja/webhooks/call-incoming)
    設定交換は、電話通話の最初の試行では **10 s**、1 回の
    再試行を含む交換全体では **13 s**、ウィジェットセッションでは
    **10 s** に制限されます。応答が遅いと通話の応答が遅延するため、
    数秒以内に応答することを目標にしてください。Webhook モードの[ツールディスパッチ](/ja/tools/overview)では、
    デフォルトで 20 s が許可され、ツール宣言ではトップレベルの `timeout` を設定できます。
  </Accordion>

  <Accordion title="送信元 IP">
    送信 Webhook は ThunderPhone のクラウド IP 範囲から送信されます。
    ファイアウォールで許可リストが必要な場合は、サポートにお問い合わせください。最新の
    範囲を共有します。
  </Accordion>
</AccordionGroup>

## 従来型 Webhook とエンドポイントベース Webhook の選択

| 機能 | レガシー (`/v1/webhook`) | エンドポイント (`/v1/developer/webhook-endpoints`) |
|---------|------------------------|----------------------------------------------|
| URL数 | 組織ごとに1つ | 組織ごとに複数 |
| イベント対応範囲 | `telephony.*` / `web.*` のみ | 全10種類のイベント |
| イベントフィルター | — | エンドポイントごと |
| 再試行 | 電話通話設定の交換を除きなし | 24時間で8回試行 |
| エンベロープ | `type` + `data` | `type` + `data` + `event_id` |
| シークレットのローテーション | 単一のシークレットを置換 | エンドポイントごとのシークレット |
| 削除せずに無効化 | `{"url": ""}` を指定した `PUT /v1/webhook` | `status=disabled` |
| ステータスの可視性 | — | `active` / `disabled` / `failing` |
| ブロッキング設定交換 | あり（[`telephony.incoming` / `web.incoming`](/ja/webhooks/call-incoming)） | なし — 通知のみ |
| 最適な用途 | 動的な通話設定 | 本番環境でのイベント処理 |

新しい統合では、エンドポイントベースの
Webhook を通じてイベントを処理してください。通話応答時に動的に設定する場合、
または Webhook モードのツールディスパッチを使用する場合にのみ、従来型 URL を維持または追加してください。これらの
リクエスト/レスポンス交換は従来のパスでのみ実行されます。

---

## 関連

<CardGroup cols={2}>
  <Card title="イベントカタログ" icon="list" href="/ja/webhooks/events">
    すべてのイベントタイプとそのペイロード。
  </Card>
  <Card title="Webhookエンドポイント" icon="bolt" href="/ja/webhooks/endpoints">
    複数のエンドポイント、イベントフィルター、シークレットを管理。
  </Card>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/ja/webhooks/call-incoming">
    通話を設定するためにサーバーが応答する必要があるブロッキングリクエスト。
  </Card>
  <Card title="telephony.complete / web.complete" icon="phone" href="/ja/webhooks/call-complete">
    文字起こし、録音、メトリクスを含む通話後のペイロード。
  </Card>
</CardGroup>
