---
title: "通話ごとの動的設定"
description: "管理するWebhookのカスタムロジックに基づき、着信ごとに応答するエージェントを選択するか、そのプロンプトと設定を書き換えます。"
---

デフォルトでは、すべての電話番号と公開可能キーに固定エージェントが割り当てられます。**発信者ごとまたは訪問者ごとのカスタマイズが必要な場合**、たとえば VIP ルーティング、ログイン済みユーザーのコンテキスト、A/B プロンプトテストでは、Webhook モードに切り替え、サーバー側で判断します。

## 仕組み

1. [`telephony.incoming`](/ja/webhooks/events)
   （電話）または [`web.incoming`](/ja/webhooks/events)（ウィジェット）
   イベントを購読します。どちらも **ブロッキング** Webhook です。ThunderPhone は通話を
   続行する前に応答を待機します。電話では最大 13 秒（1 回の再試行を含む）、
   ウィジェットセッションでは 10 秒です。
2. ThunderPhone は `{call_id, from_number, to_number}` を送信します（ウィジェット
   セッションでは番号の代わりにウィジェット固有のフィールドが送信されます。詳細は
   [リクエストスキーマ](/ja/webhooks/call-incoming)を参照してください）。
3. サーバーはエージェント設定（プロンプト、音声、プロダクト、ツール）を返します。
   ThunderPhone はその設定を通話に使用します。
4. フォールバックエージェントはありません。`{}` または無効な設定を返した場合、
   または [再試行](/ja/webhooks/call-incoming)後もリクエストが失敗する場合、
   電話は直ちに終了し、`end_reason`
   `inbound_config_webhook_failed` として記録されます。ウィジェットセッションのリクエストは失敗します。

<Note>
  Webhook エンドポイントに配信する場合でも、従来の単一 URL Webhook に配信する場合でも、電話通話
  （`telephony.incoming`）とウィジェットセッション（`web.incoming`）で同じように動作します。
</Note>

<Warning>
  **インライン Webhook 設定の通話では、ThunderPhone の同意告知は再生されません。** この経路ではエージェントレベルの通話開始告知を
  バイパスし、ThunderPhone の同意告知フレームワーク（利用規約の「録音と同意」セクション）から明示的に除外されます。これらの通話における録音、モニタリング、AI 参加、発信者識別に関するすべての通知と同意は、
  組織の責任で管理してください。インライン Webhook モードを有効にする前に、
  独自のフローで提供してください。たとえば、プロンプトのオープニングスクリプトに含めます。保存済みの
  `agent_id` を参照する Webhook 応答では、そのエージェントの通常の録音および開示ポリシーが使用されます。
</Warning>

## 1. Webhook の送信先を設定する

<Tabs>
<Tab title="電話通話">
電話番号の場合、エンドポイントで `telephony.incoming` を購読します。

```bash
curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label":  "Prod call-incoming",
    "url":    "https://example.com/thunderphone/incoming",
    "events": ["telephony.incoming"]
  }'
```

応答には一度だけ表示される `secret` が含まれます。保存して、署名検証に使用してください。
</Tab>
<Tab title="Web ウィジェット">
ウィジェットセッションの場合、エンドポイント URL を埋め込んだ `mode="webhook"` の公開可能キーを作成します。

```bash
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Dynamic widget",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-incoming",
    "allowed_domains": ["example.com"]
  }'
```

ウィジェットは、セッション開始時に毎回この URL に POST します。
</Tab>
</Tabs>

## 2. ハンドラーを実装する

3つの基本原則:

- すべてのリクエストで**署名を検証**します（[Webhook 署名を検証する](/ja/guides/verify-webhook-signatures)を参照）。
  開発環境でも省略せず、一度正しく実装して再利用します。
- **迅速に応答**します。通話の初回試行は10秒、やり取り全体は13秒で打ち切られ、その間の1秒ごとに発信者には無音が続きます。必要に応じてデータベースを参照しても構いませんが、下流のLLMを同期的に呼び出さないでください。動的にプロンプトを生成する場合は、事前計算してキャッシュします。
- **適切にフォールバック**します。想定外の状態では、デフォルトの`prompt`と`voice`（または保存済みの`agent_id`）を返します。`{}`またはエラーステータスを返すと通話が終了します。

<CodeGroup>
```python FastAPI
import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]

def verify(body: bytes, sig: str) -> bool:
    expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig or "")

@app.post("/thunderphone/incoming")
async def incoming(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(401)

    event = json.loads(body)
    if event["type"] not in ("telephony.incoming", "web.incoming"):
        return {}  # fall back to default

    caller = event["data"]["from_number"]
    # Cheap DB lookup: is this a known VIP?
    customer = lookup_customer(caller)
    if customer and customer.tier == "vip":
        return {
            "prompt":  f"You are a VIP concierge for {customer.name}. Be proactive…",
            "voice":   "john",
            "product": "storm-base",
        }
    return {}  # default agent handles non-VIPs

def lookup_customer(phone: str):
    # ... your CRM integration ...
    pass
```

```javascript Express
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;

function verify(body, sig) {
  const expected = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
  return sig &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

app.post(
  "/thunderphone/incoming",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));

    const IMPORTANT_TYPES = new Set([
      "telephony.incoming",
      "web.incoming",
    ]);
    if (!IMPORTANT_TYPES.has(event.type)) return res.json({});

    const customer = await lookupCustomer(event.data.from_number);
    if (customer?.tier === "vip") {
      return res.json({
        prompt:  `You are a VIP concierge for ${customer.name}. Be proactive…`,
        voice:   "john",
        product: "storm-base",
      });
    }
    res.json({}); // fall back to default agent
  },
);
```
</CodeGroup>

## 3. レスポンススキーマ

レスポンス本文は
[着信通話レスポンススキーマ](/ja/webhooks/call-incoming)
と完全に一致します。よく使用されるフィールド:

| フィールド | 型 | 説明 |
|-------|------|-------------|
| `prompt` | 文字列（必須） | エージェントのシステムプロンプト |
| `voice` | 文字列（必須） | [`GET /v1/voices`](/api-reference/agents#voices) の音声 ID |
| `product` | 文字列 | デフォルトは `spark`。実験的な製品は、許可リストに登録された組織でのみ利用できます。 |
| `background_track` | 文字列 \| null | 環境音声 ID |
| `acknowledgement_prompt_mode` | 文字列 | `auto` または `manual`（確認応答対応の Storm のみ） |
| `acknowledgement_prompt` | 文字列 | モードが `manual` の場合は必須 |
| `language_switching` | 文字列 | 多言語エージェントでは、`ask_first` または `follow_caller` |
| `tools` | 配列 | インライン関数ツールのスキーマ — [関数ツール](/ja/tools/overview)を参照 |

<Note>
  通話ごとの発話順序と `max_hold_seconds` は
  webhook レスポンスでは使用できません。参照する
  [エージェント](/api-reference/agents)で設定してください。
</Note>

### 保存済みエージェントを維持して変数を指定する

組織の保存済みエージェントを通話ごとのデータとともに使用するには、`{"agent_id": 12, "variables": {"name": "Ada"}}` を返します。
そのプロンプトには `{{name}}` または `{{name|Friend}}` を含めることができます。Webhook 変数はリクエストレベルの変数にマージされます。null の場合はプレースホルダーのデフォルト値が使用され、指定されていない場合は空のテキストが使用されます。最終的な値と未解決の名前は、通話詳細と完了 Webhook に表示されます。
保存済みエージェントのレスポンスで受け付けられるのは `agent_id`、`variables`、`timezone` のみです。`prompt` が
存在する場合、レスポンスはインライン設定を使用し、`agent_id` を無視します（null または整数以外のメタデータを含む
場合も同様です）。インラインプロンプトは引き続き有効である必要があります。インライン設定のレスポンスには `variables` を含めることもできます。保存済みエージェントのレスポンスでは、電話とウィジェットの両方の通話でエージェントにデプロイされた A/B 分割を使用してから、変数をレンダリングします。制限事項とセッション API のサポートについては、[通話変数](/ja/guides/call-variables)を参照してください。
ブロッキング設定は、レガシーの電話番号/組織 URL または Webhook モードのウィジェットキーから取得されます。エンドポイントシステムの着信イベントは通知のみです。

### 通話ごとにタイムゾーンを設定

1つのエージェントで、異なるタイムゾーンの連絡先に応答できます。保存済みエージェントまたはインラインレスポンスにIANA名である`timezone`を追加すると、この通話ではエージェント自身のタイムゾーンより優先して、そのゾーンの日付と時刻を伝え、そのゾーンの時間帯を提案します。

```json
{"agent_id": 12, "timezone": "America/Chicago", "variables": {"name": "Ada"}}
```

`variables`にはタイムスタンプではなくゾーンを送信してください。通話中もエージェントの時計は進み続けるため、渡した時刻は古くなります。IANAゾーンではない値は無視され、通話はエージェントのゾーンで接続されます。

## パターン

### ログイン済みユーザーのコンテキスト

webhook モードのウィジェットでは、訪問者が誰であるかはすでに
ページ側で把握されています。ウィジェット SDK が転送するクエリ文字列パラメータ
（`?customer_id=123`）を付けて webhook を呼び出し、サーバー側で顧客を検索します。

### A/B プロンプトのロールアウト

これを独自実装する前に、ThunderPhone にはネイティブの
[実験](/ja/guides/concepts)機能
（`/dashboard/experiments` とエージェントビルダーの **A/B** タブ）があることに注意してください。
この機能ではバリアントを定義し、トラフィックを分割し、バリアントごとの結果を比較できます —
webhook は不要です。

それでも webhook 側で制御する必要がある場合: `call_id` をハッシュ化してバケットに割り当て、
`0..49` にはプロンプト A、`50..99` にはプロンプト B を提供します。選択した
バケットを独自の DB に記録し、後で完了した通話の評価と関連付けます。

### 時間ベースのルーティング

営業時間内 → 「ライブサポート」エージェント、営業時間外 → 「メッセージ受付」
エージェント。ハンドラー内で `new Date().getUTCHours()` による単純な切り替えを行います。

---

## 次のステップ

<CardGroup cols={2}>
  <Card title="着信通話 webhook リファレンス" icon="phone" href="/ja/webhooks/call-incoming">
    すべての設定キーを含む、正確なリクエストおよびレスポンススキーマ。
  </Card>
  <Card title="webhook 署名を検証する" icon="shield-check" href="/ja/guides/verify-webhook-signatures">
    HMAC を一度正しく実装すれば、どこでも再利用できます。
  </Card>
  <Card title="ツール統合を構築する" icon="screwdriver-wrench" href="/ja/guides/build-tool-integration">
    動的ルーティングとエージェントごとのツールを組み合わせます。
  </Card>
  <Card title="配信セマンティクス" icon="bolt" href="/ja/webhooks/overview">
    再試行、順序、タイムアウト。
  </Card>
</CardGroup>
