> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# カスタムチャネル

## 概要

Custom Channelは、外部メッセージングシステムをElevenLabsエージェントに接続します。ユーザーメッセージをElevenLabsのWebhookに送信し、エージェントの返信を独自のHTTPSエンドポイントで受信できます。

> **Warning**
>
> Custom Channelはアルファ版です。

> **Warning**
>
> Custom Channelは、ゼロ保持モードを使用するエージェントまたはワークスペースでは利用できません。

## 機能

| 機能            | サポート                                  |
| ------------- | ------------------------------------- |
| ゼロ保持モード（ZRM）  | 非対応 — ZRMワークスペースおよびZRMエージェントでは利用できません |
| メッセージ内の添付ファイル | 非対応 — メッセージはテキストのみです                  |

## セットアップ

#### Custom Channelを開く

エージェントを開き、**Channels**を選択して**Custom Channel**を選び、**Add trigger**をクリックします。

#### トリガーを設定する

既存の接続を選択するか新規作成し、**Reply Webhook URL**を入力します。

#### 認証情報をコピーする

**Add**をクリックし、**Inbound Webhook URL**、**Inbound Secret**、**Outbound Signing Secret**をコピーします。

#### サービスを設定する

`X-Webhook-Secret`にインバウンドシークレットを指定して、インバウンドWebhook URLへユーザーメッセージを送信します。アウトバウンド署名シークレットを使用して、各返信を検証します。

## メッセージを送信する

生成されたWebhook URLに`POST`リクエストを送信します。

```text
POST /v1/convai/api-integrations/custom_channel/triggers/{trigger_connection_id}/async_message
X-Webhook-Secret: <inbound-secret>
Content-Type: application/json
```

```json
{
  "data": {
    "type": "user_message",
    "text": "Where is my order?",
    "user_identifier": "customer_8427"
  },
  "user_message_id": "msg_01k1e6z3f4t8n9c2",
  "dynamic_variables": {
    "order_id": "order_72491"
  }
}
```

| フィールド                  | 必須  | 説明                                       |
| ---------------------- | --- | ---------------------------------------- |
| `data.type`            | はい  | `user_message`である必要があります。                |
| `data.text`            | はい  | 空でないユーザーメッセージ。                           |
| `data.user_identifier` | いいえ | 外部ユーザーの識別子。                              |
| `user_message_id`      | はい  | システムから指定する、空でない冪等性キー。                    |
| `conversation_id`      | いいえ | 会話を継続するには返されたIDを含めます。新しい会話を開始する場合は省略します。 |
| `dynamic_variables`    | いいえ | このターンでエージェントに渡す動的変数。                     |

ElevenLabsは、ターンを処理する前に`202 Accepted`を返します。

```json
{
  "conversation_id": "conv_01k1e72d4x8p6v3m",
  "status": "queued"
}
```

会話を継続するには、その`conversation_id`と新しい`user_message_id`を指定して別のリクエストを送信します。

## 返信を受信する

ElevenLabsは各ターンの後、返信Webhook URLに`POST`リクエストを送信します。

```json
{
  "version": "1",
  "conversation_id": "conv_01k1e72d4x8p6v3m",
  "user_message_ids": ["msg_01k1e6z3f4t8n9c2"],
  "status": "completed",
  "data": [
    {
      "type": "agent_response",
      "event": {
        "agent_response": "Your order is scheduled to arrive tomorrow.",
        "response_id": "9f2c1a7e-4b3d-4e8a-9c1f-2d6b8e0a5f31",
        "event_id": 4
      }
    },
    {
      "type": "agent_tool_response",
      "event": {
        "tool_name": "end_call",
        "tool_call_id": "toolu_01k1e70r4b8y",
        "tool_type": "system",
        "event_id": 4,
        "is_called": true,
        "is_error": false,
        "is_blocked": false,
        "status": "success"
      }
    }
  ],
  "error": null
}
```

処理に失敗した場合、`status`は`failed`、`data`は`[]`となり、`error`に説明が含まれます。

`data`にはターン順にイベントが並びます。各項目には`type`と`event`があります。

* `agent_response`には1つのエージェント発話が含まれます。`response_id`は発話を一意に識別し、`event_id`は発話をターンに関連付けます。チャンネルでターンごとに1つのテキストバブルを表示する場合は、`agent_response`の値を連結してください。
* `agent_tool_response`はツールの結果を報告し、ターンの`event_id`を共有します。`status`は`success`、`error`、`blocked`、または`skipped`です。`tool_type: "system"`、`tool_name: "end_call"`、`status: "success"`のレスポンスは、エージェントが会話を終了したことを示します。

複数のインバウンドメッセージが1つのターンにまとめられる場合があります。`user_message_ids`には、この返信が応答するユーザーメッセージIDの一覧が含まれます。

## 返信署名を検証する

各返信には`ElevenLabs-Signature`ヘッダーが含まれます。

```text
t=1753876800,v0=<hex-digest>
```

ダイジェストは、アウトバウンド署名シークレットを使用して`{timestamp}.{raw_request_body}`に対して生成されるHMAC-SHA256署名です。JSONを解析する前に生の本文を検証し、古いタイムスタンプは拒否してください。

```python
import hashlib
import hmac
import time


def verify_signature(raw_body: bytes, header: str, secret: str) -> None:
    values = dict(part.split("=", 1) for part in header.split(","))
    timestamp = values["t"]
    if abs(time.time() - int(timestamp)) > 30 * 60:
        raise ValueError("Stale webhook signature")

    expected = hmac.new(
        secret.encode(),
        timestamp.encode() + b"." + raw_body,
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(expected, values["v0"]):
        raise ValueError("Invalid webhook signature")
```

```typescript
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifySignature(rawBody: Buffer, header: string, secret: string): void {
  const values = Object.fromEntries(header.split(",").map((part) => part.split("=", 2)));
  const timestamp = values.t;
  if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > 30 * 60) {
    throw new Error("Stale webhook signature");
  }

  const expected = createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest();
  const received = Buffer.from(values.v0 ?? "", "hex");
  if (received.length !== expected.length || !timingSafeEqual(expected, received)) {
    throw new Error("Invalid webhook signature");
  }
}
```

## 配信動作

ElevenLabsは、約0秒、0.5秒、2秒後にプロセス内で3回配信を試みます。`2xx`レスポンスを受け取ると、配信成功と見なされます。

リクエスト本文は256KiBに制限されています。