# セッションウェブフック

## 概要

セッションウェブフックは、セッションの状態を送信するウェブフックです。

例えばチャネルに誰も接続していない状態で新しく接続されたタイミングや、
誰も接続しない状態が一定時間続いたタイミングで、
ウェブフックを利用してセッションの状態などを HTTP リクエストで送信する機能です。

また、セッション開始時にウェブフックの戻り値で録画開始や音声ストリーミングなどを指定することができ、
今まで API を叩いていた処理をウェブフックで行うことができるようになります。

## 目的

セッションは実際にチャネルに参加しているクライアントの集まりです。

もともと Sora にはチャネルを作るという概念がありません。
チャネルに誰も接続していない状態で、新しく接続があれば、チャネルが利用されている状態になります。

そのため、チャネルが利用されているかどうかの判断は HTTP API を利用したり、
イベントウェブフックリクエストの送信を利用したりして、アプリケーション側で判断する必要がありました。

このセッションウェブフックは、新しくチャネルに状態を持たせ、
その状態をウェブフックリクエストで送信することで、API やイベントウェブフックを利用せずに、
チャネルの状態を判断することができるようになります。

## セッション ID

特定の期間チャネルに接続していたクライアントをグルーピングするために、 Sora はセッション ID という値を払い出します。
セッション ID は Sora が指定するため、書き換えることはできません。

セッション ID は UUIDv4 を Base32 でエンコードした 26 バイトの文字列です。

## ログ

セッションウェブフックのログは `log/session_webhook.jsonl` と `log/session_webhook_error.jsonl` に出力されます。
これは [session_webhook_url](SORA_CONF.html#76fa79) を指定していなくても出力されます。

### session_webhook.jsonl

セッションウェブフックで送信した **すべて** の処理を書き込みます。

### session_webhook_error.jsonl

セッションウェブフックの送信が正常に処理できなかった処理を書き込みます。

- ステータスコードが 200 番台以外の場合
- タイムアウトした場合

## 設定

### session_webhook_url

セッションウェブフックリクエストの送信先を指定してください。

```ini
session_webhook_url = https://example.com/sora/session/webhook
```

### session_created_timeout

セッションが存在しない状態で、新しくセッションを生成する際の制限時間です。
これはセッションウェブフック処理も含めた時間で、この時間を超えるとタイムアウトとなり、切断されます。

デフォルトでは 5 秒です。

```ini
session_created_timeout = 5 s
```

### session_destroyed_timeout

そのチャネルの接続数が 0 になってから新しい接続が無い場合に、 `session.destroyed` のセッションウェブフックリクエストを送信するまでの時間を指定します。

デフォルトでは 15 秒です。

```ini
session_destroyed_timeout = 15 s
```

### session_updated_webhook_interval

`session.updated` の送信間隔を指定します。

デフォルトでは `1 min` (1 分) です。単位は `min` しか指定できません。

```ini
session_updated_webhook_interval = 1 min
```


## ログ

セッションウェブフックのログは `log/session_webhook.jsonl` と `log/session_webhook_error.jsonl` に出力されます。
これは [session_webhook_url](SORA_CONF.html#76fa79) を指定していなくても出力されます。

### session_webhook.jsonl

セッションウェブフックの送信が **正常に動作した** 処理を書き込みます。

#### 項目

- id - Base32 UUIDv4
- timestamp- RFC 3339 UTC マイクロ秒
- req - リクエスト
  - センシティブデータはマスクされます
- res - レスポンス
  - センシティブデータはマスクされます
- url- セッションウェブフック URL
  - セッションウェブフックが指定されていなければ含まれません

### session_webhook_error.jsonl

セッションウェブフックの送信が正常に処理できなかった処理を書き込みます。

- ステータスコードが 200 番台以外の場合
- タイムアウトした場合

#### 項目

- id - Base32 UUIDv4
- timestamp- RFC 3339 UTC マイクロ秒
- req - リクエスト
  - センシティブデータはマスクされません
- reason - ウェブフックエラー理由
- url- セッションウェブフック URL
  - セッションウェブフックが指定されていなければ含まれません


## `spotlight` が既存セッションと異なる場合の挙動

既存セッションが存在する場合、 `spotlight` が異なる新規接続が来た場合は、
`INVALID-MESSAGE` エラーを返し切断するようになります。


## session.created

**セッション生成**

同時接続数が 0 のチャネル ID に対して、新しい接続の認証が成功したタイミングで `session.created` ウェブフックリクエストを送信します。

チャネルの同時接続数が 0 の場合でも、 `spotlight` の値が一致するセッションが過去に生成されており、
まだ破棄されていない場合には、そのセッションが使用されるため `session.created` ウェブフックは送信されません。

* - キー
  - 型
  - 内容
* - type
  - string
  - "session.created"
* - id
  - string
  - ウェブフック ID (ユニーク)
* - label
  - string
  - sora.conf の label にて指定した値
* - node_name
  - string
  - Sora ノード名
* - version
  - string
  - Sora のバージョン
* - channel_id
  - string
  - チャネル ID
* - session_id
  - string
  - セッション ID
* - timestamp
  - string (RFC3339 UTC マイクロ秒)
  - ウェブフック作成時間
* - created_time
  - integer (UNIX 時間)
  - セッション生成時間
* - created_timestamp
  - string (RFC3339 UTC マイクロ秒)
  - セッション生成時間
* - spotlight
  - boolean
  - スポットライトが有効かどうか
* - external_signaling_url
  - string
  - (クラスター機能が有効の場合) 接続先の external_signaling_url

```javascript
{
  "channel_id": "sora",
  "created_time": 1638337454,
  "created_timestamp": "2021-12-01T05:44:14.523736Z",
  "id": "2YN2DQE5KD3GSFSVPAYZ7VTAV4",
  "label": "WebRTC SFU Sora",
  "spotlight": false,
  "node_name": "sora@127.0.0.1",
  "session_id": "NPR769YPQ914K10FW42PGH4TKW",
  "timestamp": "2021-12-01T05:44:14.523912Z",
  "external_signaling_url": "wss://node-01.example.com/signaling",
  "type": "session.created",
  "version": "2023.2.0"
}
```

### 戻り値

セッションウェブフックは `"type": "session.created"` の戻り値を指定できます。

#### session_metadata

> **注釈**
>
> この項目はセンシティブデータとして扱われます。

`session_metadata` を指定した場合、セッション更新時の `session.updated` とセッション破棄時の `"type": "session.destroyed"` のウェブフックリクエストに `session_metadata` が含まれます。

```javascript
{
  "session_metadata": "<JSON>"
}
```


#### session_lifetime

セッション生成のタイミングで、セッションのライフタイムを指定することができます。

例えば `3600` を指定した場合、 3600 秒後にセッションは破棄されます

```javascript
{
  "session_lifetime": 3600
}
```

- `session_lifetime` の指定にかかわらず、セッションの同時接続数が 0 になると、 [session_destroyed_timeout](SORA_CONF.html#642cc4) に指定した時間が経過したタイミングでセッションが破棄されます
- **秒** で指定してください
- 最大 2,592,000 秒 (30 日) まで指定できます
- セッション生成後に変更することはできません
- `session_lifetime` が未指定の場合は **無制限** です
- `session.destroyed` の `reason` に `lifetime_expired` が入ります
- `connection.destroyed` の `reason` に `session_destroyed` が入ります
- セッションのライフタイムが終了すると、セッションが破棄され切断します
- セッションのライフタイムが終了すると、セッションウェブフック `session.destroyed` が送信されます


#### spotlight_number

セッション生成のタイミングで、スポットライト機能が有効な場合に、スポットライトの数を指定することができます。

クライアント単位でのシグナリング接続時や認証ウェブフック成功時の `spotlight_number` と異なる値を払い出した場合、
エラーとなりますので注意してください。 `spotlight_number` はセッションウェブフックでの指定を推奨します。

セッション単位の `spotlight_number` を変更するには [ChangeSpotlightNumber](API_SPOTLIGHT.html#17e930) API を利用する必要があります。

```javascript
{
  "spotlight_number": 3
}
```


#### group_id

セッション生成のタイミングで、セッションをグルーピングする `group_id` を指定することができます。

[ListSessions](API_SESSION.html#748f19) API にて `group_id` を指定することで、指定した `group_id` のセッションのみを返します。

`group_id` は 1 バイト以上 255 バイト以下の文字列です。未指定の場合は `session_id` が入ります。

```javascript
{
  "group_id": "group_01"
}
```


#### duplicate_client_id

セッション生成のタイミングで、
クライアント ID が重複した場合の挙動を `duplicate_client_id` で指定することができます。

`duplicate_client_id` に `evict` を指定した場合、
既に接続している同一クライアント ID の接続を破棄するような挙動になります。

`duplicate_client_id` には `allow` または `evict` を指定できます。

- `allow` は今まで通り重複を許容します
- `evict` は既存の接続を破棄し、新規接続を受け入れます、つまり既存接続を追い出します

未指定の場合は `sora.conf` の [default_duplicate_client_id](SORA_CONF.html#c8b57b) の値を利用します。

```javascript
{
  "duplicate_client_id": "evict"
}
```


#### max_connections

- `max_connections` を指定した場合、セッション単位での最大同時接続数が指定した値になります。
- 途中で最大同時接続数を変更することはできません。指定できる範囲は `0..10000` です
- `max_connections` が `0` の場合は誰も接続することができなくなります
- セッションが同時接続数制限に達した場合はクライアントに `SERVICE-UNAVAILABLE` を通知します
- セッションが同時接続数制限に達した場合は [ignore_connection_failed_webhook](SORA_CONF.html#45096a) が `false` の場合は [connection.failed](EVENT_WEBHOOK.html#5266b6) が送信されます> - `message` に `"SERVICE-UNAVAILABLE"` が含まれます

```javascript
{
  "max_connections": 3
}
```

#### recording

`recording` を `true` を指定した場合、セッション単位の録画を開始します。

詳細は [録画機能 (セッション単位) の session.created](RECORDING.html#3cec61) をご確認ください。

```javascript
{
  "recording": true
}
```


#### recording_expire_time

- `recording` が `true` の場合のみ有効です。録画の有効期限を秒単位で指定します
- `recording_expire_time` を `3600` を指定した場合、 3600 秒後に録画は終了します

詳細は [録画機能 (セッション単位) の session.created](RECORDING.html#3cec61) をご確認ください。

```javascript
{
  "recording_expire_time": 3600
}
```


#### recording_split_only

- `recording` が `true` の場合のみ有効です。録画を分割のみで行うかどうかを指定します
- `recording_split_only` を `true` に指定した場合、録画は分割のみで行われます

詳細は [録画機能 (セッション単位) の session.created](RECORDING.html#3cec61) をご確認ください。

```javascript
{
  "recording_split_only": true
}
```


#### recording_split_duration

- `recording` が `true` の場合のみ有効です。分割録画の分割時間を秒単位で指定します
- `recording_split_duration` を `60` を指定した場合、 60 秒ごとに録画が分割されます

詳細は [録画機能 (セッション単位) の session.created](RECORDING.html#3cec61) をご確認ください。

```javascript
{
  "recording_split_duration": 60
}
```


#### recording_metadata

`recording` が `true` の場合のみ有効です。recording.report ウェブフックやレポートファイルに含まれます。

詳細は [録画機能 (セッション単位) の session.created](RECORDING.html#3cec61) をご確認ください。

```javascript
{
  "recording_metadata": {"spam": "egg"}
}
```


#### recording_format

`recording` が `true` の場合のみ有効です。録画ファイルのフォーマットを指定します。

`webm` と `mp4` が指定できます。

詳細は [録画機能 (セッション単位) の session.created](RECORDING.html#3cec61) をご確認ください。

```javascript
{
  "recording_format": "mp4"
}
```

#### forwarding_filters

`forwarding_filters` を指定した場合、転送フィルターをセッションに対して反映することができます。

転送フィルタールールの詳細については [転送フィルター機能](FORWARDING_FILTER.html) をご確認ください。

```javascript
{
  "forwarding_filters": [
    {
      "action": "allow",
      "rules": [
        // チャネルに接続しているすべてのコネクションに音声のみ転送する
        [
        {"field": "kind", "operator": "is_in", "values": ["audio"]}
        ]
      ]
    }
  ]
}
```

#### audio_streaming

`audio_streaming` を `true` に指定した場合、そのセッションで音声ストリーミングが有効になります。

音声ストリーミング機能の詳細については [音声ストリーミング機能](AUDIO_STREAMING.html) をご確認ください。

```javascript
{
  "audio_streaming": true
}
```

#### audio_streaming_auto

`audio_streaming_auto` を `true` に指定した場合、そのセッションで音声ストリーミングが有効になり、
かつ自動開始、自動停止機能が有効になります。
この設定を `true` にするときには `audio_streaming` も `true` に設定する必要があります。

詳細は [audio_streaming 関連の払い出し設定の組み合わせによる動作](AUDIO_STREAMING.html#e28478) をご確認ください。

```javascript
{
  "audio_streaming": true,
  "audio_streaming_auto": true
}
```


## session.destroyed

**セッション破棄**

同時接続数が 0 になったチャネル ID が一定時間経過したタイミングで、 `session.destroyed` ウェブフックリクエストを送信します。

* - キー
  - 型
  - 内容
* - type
  - string
  - "session.destroyed"
* - id
  - string
  - ウェブフック ID (ユニーク)
* - label
  - string
  - sora.conf の label にて指定した値
* - node_name
  - string
  - Sora ノード名
* - version
  - string
  - Sora のバージョン
* - channel_id
  - string
  - チャネル ID
* - group_id
  - string
  - セッションをグルーピングする ID
* - session_id
  - string
  - セッション ID
* - timestamp
  - string (RFC3339 UTC マイクロ秒)
  - ウェブフック作成時間
* - created_time
  - integer (UNIX 時間)
  - セッション生成時間
* - created_timestamp
  - string (RFC3339 UTC マイクロ秒)
  - セッション生成時間
* - destroyed_time
  - integer (UNIX 時間)
  - セッション破棄時間
* - destroyed_timestamp
  - string (RFC3339 UTC マイクロ秒)
  - セッション破棄時間
* - spotlight
  - boolean
  - スポットライトが有効かどうか
* - spotlight_number
  - integer
  - スポットライトが有効な場合、スポットライト数
* - total_connections
  - integer
  - 延べ接続数
* - max_connections
  - integer
  - 最大同時接続数
* - connections
  - array (object)
  - (クラスター機能が無効の場合) セッションに参加した接続情報
* - session_metadata
  - json
  - (値がある場合) session.created の戻り値で指定した値
* - external_signaling_url
  - string
  - (クラスター機能が有効の場合) 接続先の external_signaling_url
* - reason
  - string
  - セッションが破棄された理由 ("normal", "validation_error", "webhook_error", "terminated_api", "lifetime_expired", "abort")

```javascript
{
  "channel_id": "sora",
  "connections": [
    {
      "audio": true,
      "audio_codec_type": "OPUS",
      "client_id": "W9QE86Z0BS1QFFPZ2QB5DRZ2HC",
      "bundle_id": "W9QE86Z0BS1QFFPZ2QB5DRZ2HC",
      "connection_id": "W9QE86Z0BS1QFFPZ2QB5DRZ2HC",
      "connection_created_timestamp": "2021-12-01T05:44:23.051704Z",
      "connection_destroyed_timestamp": "2021-12-01T05:44:57.083215Z",
      "role": "sendrecv",
      "simulcast": false,
      "video": true,
      "video_bit_rate": 1000,
      "video_codec_type": "VP9",
      "video_vp9_params": { "profile_id": 0 },
    },
    {
      "audio": true,
      "audio_codec_type": "OPUS",
      "client_id": "JXMYW6GPX54EH0HGA5X4130FBM",
      "bundle_id": "JXMYW6GPX54EH0HGA5X4130FBM",
      "connection_id": "JXMYW6GPX54EH0HGA5X4130FBM",
      "connection_created_timestamp": "2021-12-01T05:44:21.500272Z",
      "connection_destroyed_timestamp": "2021-12-01T05:44:56.878019Z",
      "role": "sendrecv",
      "simulcast": false,
      "video": true,
      "video_bit_rate": 1000,
      "video_codec_type": "VP9",
      "video_vp9_params": { "profile_id": 0 },
    },
    {
      "audio": true,
      "audio_codec_type": "OPUS",
      "client_id": "F8VK9R71BN5S5EDE737C8XAA3C",
      "bundle_id": "F8VK9R71BN5S5EDE737C8XAA3C",
      "connection_id": "F8VK9R71BN5S5EDE737C8XAA3C",
      "connection_created_timestamp": "2021-12-01T05:44:19.811385Z",
      "connection_destroyed_timestamp": "2021-12-01T05:44:55.897819Z",
      "role": "sendrecv",
      "simulcast": false,
      "video": true,
      "video_bit_rate": 1000,
      "video_codec_type": "VP9",
      "video_vp9_params": { "profile_id": 0 },
    },
    {
      "audio": true,
      "audio_codec_type": "OPUS",
      "client_id": "VBYS5TV5S510F98GS2VK015AKG",
      "bundle_id": "VBYS5TV5S510F98GS2VK015AKG",
      "connection_id": "VBYS5TV5S510F98GS2VK015AKG",
      "connection_created_timestamp": "2021-12-01T05:44:14.716974Z",
      "connection_destroyed_timestamp": "2021-12-01T05:44:57.197372Z",
      "role": "sendrecv",
      "simulcast": false,
      "video": true,
      "video_bit_rate": 1000,
      "video_codec_type": "VP9",
      "video_vp9_params": { "profile_id": 0 },
    }
  ],
  "created_time": 1638337454,
  "created_timestamp": "2021-12-01T05:44:14.523736Z",
  "destroyed_time": 1638337502,
  "destroyed_timestamp": "2021-12-01T05:45:02.199179Z",
  "id": "M7K1AVS9KS34V9XFJJH04TB400",
  "label": "WebRTC SFU Sora",
  "max_connections": 4,
  "node_name": "sora@127.0.0.1",
  "group_id": "NPR769YPQ914K10FW42PGH4TKW",
  "session_id": "NPR769YPQ914K10FW42PGH4TKW",
  "spotlight": false,
  "timestamp": "2021-12-01T05:45:02.199419Z",
  "total_connections": 4,
  "session_metadata": {"spam": "egg"},
  "type": "session.destroyed",
  "version": "2023.2.0"
}
```

### connections

connections に入ってくる以下の二つは RFC3339 形式の時間ではなく `null` が入ってくる場合があります。

- connection_created_timestamp
- connection_destroyed_timestamp

これは、認証は成功し、セッションに参加はしたが、何らかの理由で WebRTC が確立に失敗した場合発生します。

両方ともの値に null が入ってきます。

### reason

session.destroyed には reason が含まれ、セッションが破棄された理由が入ります。

- `"normal"` は通常のセッション破棄
- `"validation_error"` は [session.created](SESSION_WEBHOOK.html#1d1984) の戻り値が正常ではなかったことによるエラーによるセッション破棄- このセッション破棄は [session_created_response_validate_warning_as_error](SORA_CONF.html#9df65e) が `true` の場合のみ発生します
  - 例外: [転送フィルター機能](FORWARDING_FILTER.html) に関するバリデーション失敗は、この設定に関わらずエラーになりセッション破棄になります
- `"webhook_error"` はセッション生成時のウェブフック [session.created](SESSION_WEBHOOK.html#1d1984) のウェブフック処理中にエラーが発生したことによるセッション破棄- 以下はエラー例です
  - [session_webhook_url](SORA_CONF.html#76fa79) と接続できなかった場合
  - ウェブフック送信先からステータスコードが 2XX 以外だった場合
  - ウェブフック送信先からの JSON がパースできなかった場合
  - ウェブフック送信先からの JSON のパース結果が JSON Object 以外だった場合
  - ウェブフック送信先との HTTPS に失敗した場合
  - ウェブフック送信先との mTLS に失敗した場合
- `"terminated_api"` は [TerminateSession](API_SESSION.html#ba022b) API によるセッション破棄
- `"lifetime_expired"` はセッションライフタイムの期限切れによるセッション破棄
- `"abort"` は異常発生のセッション破棄

### クラスター有効時

クラスター有効時には `connections` 項目は含まれません。

```javascript
{
  "channel_id": "sora",
  "created_time": 1638337454,
  "created_timestamp": "2021-12-01T05:44:14.523736Z",
  "destroyed_time": 1638337502,
  "destroyed_timestamp": "2021-12-01T05:45:02.199179Z",
  "id": "M7K1AVS9KS34V9XFJJH04TB400",
  "label": "WebRTC SFU Sora",
  "max_connections": 4,
  "node_name": "sora@127.0.0.1",
  "group_id": "NPR769YPQ914K10FW42PGH4TKW",
  "session_id": "NPR769YPQ914K10FW42PGH4TKW",
  "spotlight": false,
  "timestamp": "2021-12-01T05:45:02.199419Z",
  "reason": "normal",
  "total_connections": 4,
  "session_metadata": {"spam": "egg"},
  "external_signaling_url": "wss://node-01.example.com/signaling",
  "type": "session.destroyed",
  "version": "2023.2.0"
}
```


## session.updated

**セッション更新**

> **注釈**
>
> このウェブフックリクエストの送信を停止することができます。
> `sora.conf` の [ignore_session_updated_webhook](SORA_CONF.html#0901a0) を `true` にしてください。

`session.created` 送信後、一定間隔で `session.updated` ウェブフックリクエストを送信します。

送信間隔はデフォルトで 1 分です。 [session_updated_webhook_interval](SORA_CONF.html#4612dc) で送信間隔を変更できます。

`session.updated` の状態を記録することで、もし何らかの理由で `session.destroyed` が送信されなかった場合でも、
そのセッションが存在しているかどうかの判断を行う事ができるようになります。

* - キー
  - 型
  - 内容
* - type
  - string
  - "session.updated"
* - id
  - string
  - ウェブフック ID (ユニーク)
* - label
  - string
  - sora.conf の label にて指定した値
* - node_name
  - string
  - Sora ノード名
* - version
  - string
  - Sora のバージョン
* - channel_id
  - string
  - チャネル ID
* - group_id
  - string
  - セッションをグルーピングする ID
* - session_id
  - string
  - セッション ID
* - timestamp
  - string (RFC3339 UTC マイクロ秒)
  - ウェブフック作成時間
* - created_time
  - integer (UNIX 時間)
  - セッション生成時間
* - created_timestamp
  - string (RFC3339 UTC マイクロ秒)
  - セッション生成時間
* - spotlight
  - boolean
  - スポットライトが有効かどうか
* - spotlight_number
  - integer
  - スポットライトが有効な場合、スポットライト数
* - total_connections
  - integer
  - 延べ接続数
* - max_connections
  - integer
  - 最大同時接続数
* - connections
  - array (object)
  - (クラスター機能が有効の場合) セッションに参加した接続情報
* - session_metadata
  - json
  - (値がある場合) session.created の戻り値で指定した値
* - external_signaling_url
  - string
  - (クラスター機能が有効の場合) 接続先の external_signaling_url

```javascript
{
  "channel_id": "sora",
  "connections": [
    {
      "audio": true,
      "audio_codec_type": "OPUS",
      "client_id": "W9QE86Z0BS1QFFPZ2QB5DRZ2HC",
      "bundle_id": "W9QE86Z0BS1QFFPZ2QB5DRZ2HC",
      "connection_id": "W9QE86Z0BS1QFFPZ2QB5DRZ2HC",
      "connection_created_timestamp": "2021-12-01T05:44:23.051704Z",
      "connection_destroyed_timestamp": "2021-12-01T05:44:57.083215Z",
      "role": "sendrecv",
      "simulcast": false,
      "video": true,
      "video_bit_rate": 1000,
      "video_codec_type": "VP9",
      "video_vp9_params": { "profile_id": 0 },
    },
    {
      "audio": true,
      "audio_codec_type": "OPUS",
      "client_id": "JXMYW6GPX54EH0HGA5X4130FBM",
      "bundle_id": "JXMYW6GPX54EH0HGA5X4130FBM",
      "connection_id": "JXMYW6GPX54EH0HGA5X4130FBM",
      "connection_created_timestamp": "2021-12-01T05:44:21.500272Z",
      "connection_destroyed_timestamp": "2021-12-01T05:44:56.878019Z",
      "role": "sendrecv",
      "simulcast": false,
      "video": true,
      "video_bit_rate": 1000,
      "video_codec_type": "VP9",
      "video_vp9_params": { "profile_id": 0 },
    },
    {
      "audio": true,
      "audio_codec_type": "OPUS",
      "client_id": "F8VK9R71BN5S5EDE737C8XAA3C",
      "bundle_id": "F8VK9R71BN5S5EDE737C8XAA3C",
      "connection_id": "F8VK9R71BN5S5EDE737C8XAA3C",
      "connection_created_timestamp": "2021-12-01T05:44:19.811385Z",
      "connection_destroyed_timestamp": "2021-12-01T05:44:55.897819Z",
      "role": "sendrecv",
      "simulcast": false,
      "video": true,
      "video_bit_rate": 1000,
      "video_codec_type": "VP9",
      "video_vp9_params": { "profile_id": 0 },
    },
    {
      "audio": true,
      "audio_codec_type": "OPUS",
      "client_id": "VBYS5TV5S510F98GS2VK015AKG",
      "bundle_id": "VBYS5TV5S510F98GS2VK015AKG",
      "connection_id": "VBYS5TV5S510F98GS2VK015AKG",
      "connection_created_timestamp": "2021-12-01T05:44:14.716974Z",
      "connection_destroyed_timestamp": "2021-12-01T05:44:57.197372Z",
      "role": "sendrecv",
      "simulcast": false,
      "video": true,
      "video_bit_rate": 1000,
      "video_codec_type": "VP9",
      "video_vp9_params": { "profile_id": 0 },
    }
  ],
  "created_time": 1638337454,
  "created_timestamp": "2021-12-01T05:44:14.523736Z",
  "id": "M7K1AVS9KS34V9XFJJH04TB400",
  "label": "WebRTC SFU Sora",
  "max_connections": 4,
  "node_name": "sora@127.0.0.1",
  "group_id": "NPR769YPQ914K10FW42PGH4TKW",
  "session_id": "NPR769YPQ914K10FW42PGH4TKW",
  "spotlight": false,
  "timestamp": "2021-12-01T05:45:02.199419Z",
  "total_connections": 4,
  "session_metadata": {"spam": "egg"},
  "external_signaling_url": "wss://node-01.example.com/signaling",
  "type": "session.updated",
  "version": "2023.2.0"
}
```

### connections

connections に入ってくる以下の二つは RFC3339 形式の時間ではなく `null` が入ってくる場合があります。

- connection_created_timestamp
- connection_destroyed_timestamp

これは、認証は成功し、セッションに参加はしたが、何らかの理由で WebRTC が確立に失敗した場合発生します。

両方ともの値に null が入ってきます。

### クラスター有効時

クラスター有効時には `connections` 項目は含まれません。

```javascript
{
  "channel_id": "sora",
  "created_time": 1638337454,
  "created_timestamp": "2021-12-01T05:44:14.523736Z",
  "id": "M7K1AVS9KS34V9XFJJH04TB400",
  "label": "WebRTC SFU Sora",
  "max_connections": 4,
  "node_name": "sora@127.0.0.1",
  "group_id": "NPR769YPQ914K10FW42PGH4TKW",
  "session_id": "NPR769YPQ914K10FW42PGH4TKW",
  "spotlight": false,
  "timestamp": "2021-12-01T05:45:02.199419Z",
  "total_connections": 4,
  "session_metadata": {"spam": "egg"},
  "external_signaling_url": "wss://node-01.example.com/signaling",
  "type": "session.updated",
  "version": "2023.2.0"
}
```

### 録画機能 (セッション単位) 有効時

セッションで録画機能 (セッション単位) が有効な場合、 `"recording"` が含まれます。
録画が有効ではない場合は `"recording"` 項目は含まれません。

* - キー
  - 型
  - 内容
* - recording_id
  - string
  - 録画機能 (セッション単位) の ID
* - format
  - string
  - 録画機能 (セッション単位) のフォーマット
* - start_timestamp
  - string
  - セッションでの録画が開始された時刻 (RFC 3339 (UTC))
* - split_only
  - boolean
  - セッションでの録画が分割のみかどうか
* - recording_metadata (オプション)
  - JSONValue
  - 録画機能 (セッション単位) のメタデータ
* - expire_time (オプション)
  - integer
  - セッションでの録画期限
* - expired_at (オプション)
  - integer
  - セッションでの録画期限 (Unix time)
* - split_duration (オプション)
  - integer
  - セッションでの録画分割有効時の分割時間

```javascript
{
  "channel_id": "sora",
  "created_time": 1638337454,
  "created_timestamp": "2021-12-01T05:44:14.523736Z",
  "id": "M7K1AVS9KS34V9XFJJH04TB400",
  "label": "WebRTC SFU Sora",
  "max_connections": 4,
  "node_name": "sora@127.0.0.1",
  "group_id": "NPR769YPQ914K10FW42PGH4TKW",
  "session_id": "NPR769YPQ914K10FW42PGH4TKW",
  "spotlight": false,
  "timestamp": "2021-12-01T05:45:02.199419Z",
  "total_connections": 4,
  "session_metadata": {"spam": "egg"},
  "external_signaling_url": "wss://node-01.example.com/signaling",
  "type": "session.destroyed",
  "version": "2023.2.0",
  "recording": {
    "recording_id": "WHEJ888HQ55KDCFE3TZ4VPFQHR",
    "format": "mp4",
    "recording_metadata": {"spam": "egg"},
    "expire_time": 3600,
    "expired_at": 1615527737,
    "split_duration": 3600,
    "split_only": false,
    "start_timestamp": "2021-03-12T04:42:17.455668Z"
  }
}
```


## recording.started

> **重要**
>
> このウェブフックリクエストが送信されるにはセッションベースの録画を利用する必要があります

**録画開始**

- data.start_timestamp- 録画機能 (セッション単位) を開始したタイムスタンプ (RFC3339 UTC マイクロ秒) が入ります
- data.expire_time- **この項目はオプションです**
  - API による開始を行った場合は [StartRecording](API_RECORDING.html#c5b527) API で指定した `expire_time` が入ります
  - session.created ウェブフックリクエストの戻り値により録画を開始した場合は戻り値 `recording_expire_time` が入ります
  - 指定していない場合は `expire_time` の項目が入ってきません
- data.expired_at- **この項目はオプションです**
  - API による開始を行った場合は [StartRecording](API_RECORDING.html#c5b527) API で指定した `expire_time` から計算した有効期限 (UNIX Time) が入ります
  - session.created ウェブフックリクエストの戻り値により録画を開始した場合は戻り値 `recording_expire_time` から計算した有効期限 (UNIX Time) が入ります
  - `expire_time` を指定していない場合は `expired_at` の項目が入ってきません
- data.split_duration- **この項目はオプションです**
  - API による開始を行った場合は [StartRecording](API_RECORDING.html#c5b527) API で指定した `split_duration` が入ります
  - session.created ウェブフックリクエストの戻り値により録画を開始した場合は戻り値 `recording_split_duration` が入ります
  - 指定していない場合は `split_duration` の項目が入ってきません
- data.split_only- API による開始を行った場合は [StartRecording](API_RECORDING.html#c5b527) API で指定した `split_only` が入ります
  - session.created ウェブフックリクエストの戻り値により録画を開始した場合は戻り値 `recording_split_only` が入ります
  - 指定していない場合は `false` が入ります
- data.recording_metadata- **この項目はオプションです**
  - API による開始を行った場合は [StartRecording](API_RECORDING.html#c5b527) API で指定した `metadata` が入ります
  - session.created ウェブフックリクエストの戻り値により録画を開始した場合は戻り値 `recording_metadata` が入ります
  - 指定していない場合は `recording_metadata` の項目が入ってきません
- data.format- **この項目はオプションです**
  - API による開始を行った場合は [StartRecording](API_RECORDING.html#c5b527) API で指定した `format` が入ります
  - session.created ウェブフックリクエストの戻り値により録画を開始した場合は戻り値 `recording_format` が入ります
  - 指定していない場合は [default_recording_format](SORA_CONF.html#f9fa7d) の値が入ります

```javascript
{
  "type": "recording.started",
  "id": "<Base32-UUIDv4>",
  "version": "<String>",
  "label": "<String>",
  "node_name": "<String>",
  "channel_id": "<String>",
  "group_id": "<Base32-UUIDv4>",
  "session_id": "<Base32-UUIDv4>",
  "session_metadata": "<JSON>",
  "timestamp": "<UTC RFC3339 Timestamp>",
  "data": {
    "channel_id": "<String>",
    "recording_id": "<Base32-UUIDv4>",
    "recording_metadata": "<JSON-Object>",
    "split_only": "<Boolean>",
    "created_at": "<UNIX-Time>",
    "expire_time": "<Integer>",
    "expired_at": "<UNIX-Time>",
    "start_timestamp": "<UTC RFC3339 Timestamp>",
    "split_duration": "<Integer>",
    "format": "<String{webm, mp4}>"
  }
}
```


## recording.report

> **重要**
>
> このウェブフックリクエストが送信するには、セッションベースの録画を利用する必要があります

> **注釈**
>
> セッションが破棄されたタイミングでの `recording.report` は `session.destroyed` の **後** に送信されます。

**録画結果報告**

- data.archives[].start_time_offset- 録画を開始してから何秒経過した後にこの録画が開始したかを表しています
- data.archives[].stop_time_offset- 録画を開始してから何秒経過した後にこの録画が終了したかを表しています
- data.recording_metadata- StartRecording API で指定した metadata 、または `session.created` で払い出した `recording_metadata` が入ります
  - 指定していない場合は `recording_metadata` の項目が入ってきません
- data.expired_at- 期限が切れた日時を UNIX Time で返します

### 一括録画時 recording.report

```javascript
{
  "type": "recording.report",
  "id": "<Base32-UUIDv4>",
  "version": "<String>",
  "label": "<String>",
  "node_name": "<String>",
  "channel_id": "<String>",
  "group_id": "<String>",
  "session_id": "<String>",
  "session_metadata": "<JSON>",
  "timestamp": "<UTC RFC3339 Timestamp>",
  "data": {
    "channel_id": "<String>",
    "group_id": "<String>",
    "session_id": "<String>",
    "recording_id": "<Base32-UUIDv4>",
    "recording_metadata": "<JSON-Object>",
    "split_only": "<Boolean>",
    "split_duration": "<Integer>",
    "created_at": "<UNIX-Time>",
    "expire_time": "<Integer>",
    "expired_at": "<UNIX-Time>",
    "file_path": "<String>",
    "filename": "<String>",
    "file_written": "<Boolean>",
    "start_timestamp": "<UTC RFC3339 Timestamp>",
    "stop_timestamp": "<UTC RFC3339 Timestamp>",
    "format": "<String{webm, mp4}>",
    "archives": [
      {
        "label": "<String>",
        "node_name": "<String>",
        "client_id": "<String | Base32-UUIDv4>",
        "bundle_id": "<String | Base32-UUIDv4>",
        "connection_id": "<Base32-UUIDv4>",
        "file_path": "<String>",
        "filename": "<String>",
        "metadata_file_path": "<String>",
        "metadata_filename": "<String>",
        "start_time_offset": "<Integer>",
        "start_timestamp": "<UTC RFC3339 Timestamp>",
        "stop_time_offset": "<Integer>",
        "stop_timestamp": "<UTC RFC3339 Timestamp>",
        "size": "<Integer>"
      },
      {
        "label": "<String>",
        "node_name": "<String>",
        "client_id": "<String | Base32-UUIDv4>",
        "bundle_id": "<String | Base32-UUIDv4>",
        "connection_id": "<Base32-UUIDv4>",
        "file_path": "<String>",
        "filename": "<String>",
        "metadata_file_path": "<String>",
        "metadata_filename": "<String>",
        "start_time_offset": "<Integer>",
        "start_timestamp": "<UTC RFC3339 Timestamp>",
        "stop_time_offset": "<Integer>",
        "stop_timestamp": "<UTC RFC3339 Timestamp>",
        "size": "<Integer>"
      }
    ],
    "failed_archives": [
      {
        "label": "<String>",
        "node_name": "<String>",
        "client_id": "<String | Base32-UUIDv4>",
        "bundle_id": "<String | Base32-UUIDv4>",
        "connection_id": "<Base32-UUIDv4>"
      },
      {
        "label": "<String>",
        "node_name": "<String>",
        "client_id": "<String | Base32-UUIDv4>",
        "bundle_id": "<String | Base32-UUIDv4>",
        "connection_id": "<Base32-UUIDv4>"
      }
    ]
  }
}
```

### 分割録画時 recording.report

```javascript
{
  "type": "recording.report",
  "id": "<Base32-UUIDv4>",
  "version": "<String>",
  "label": "<String>",
  "node_name": "<String>",
  "channel_id": "<String>",
  "group_id": "<String>",
  "session_id": "<String>",
  "session_metadata": "<JSON>",
  "timestamp": "<UTC RFC3339 Timestamp>",
  "data": {
    "channel_id": "<String>",
    "group_id": "<String>",
    "session_id": "<String>",
    "recording_id": "<Base32-UUIDv4>",
    "recording_metadata": "<JSON-Object>",
    "split_only": "<Boolean>",
    "split_duration": "<Integer>",
    "created_at": "<UNIX-Time>",
    "expire_time": "<Integer>",
    "expired_at": "<UNIX-Time>",
    "file_path": "<String>",
    "filename": "<String>",
    "file_written": "<Boolean>",
    "start_timestamp": "<UTC RFC3339 Timestamp>",
    "stop_timestamp": "<UTC RFC3339 Timestamp>",
    "format": "<String{webm, mp4}>",
    "archives": [
      {
        "label": "<String>",
        "node_name": "<String>",
        "client_id": "<String | Base32-UUIDv4>",
        "bundle_id": "<String | Base32-UUIDv4>",
        "connection_id": "<Base32-UUIDv4>",
        "start_time_offset": "<Integer>",
        "start_timestamp": "<UTC RFC3339 Timestamp>",
        "stop_time_offset": "<Integer>",
        "stop_timestamp": "<UTC RFC3339 Timestamp>",
        "split_last_index": "<String>"
      },
      {
        "label": "<String>",
        "node_name": "<String>",
        "client_id": "<String | Base32-UUIDv4>",
        "bundle_id": "<String | Base32-UUIDv4>",
        "connection_id": "<Base32-UUIDv4>",
        "start_time_offset": "<Integer>",
        "start_timestamp": "<UTC RFC3339 Timestamp>",
        "stop_time_offset": "<Integer>",
        "stop_timestamp": "<UTC RFC3339 Timestamp>",
        "split_last_index": "<String>"
      }
    ],
    "failed_archives": [
      {
        "label": "<String>",
        "node_name": "<String>",
        "client_id": "<String | Base32-UUIDv4>",
        "bundle_id": "<String | Base32-UUIDv4>",
        "connection_id": "<Base32-UUIDv4>"
      },
      {
        "label": "<String>",
        "node_name": "<String>",
        "client_id": "<String | Base32-UUIDv4>",
        "bundle_id": "<String | Base32-UUIDv4>",
        "connection_id": "<Base32-UUIDv4>"
      }
    ]
  }
}
```


## audio-streaming.started

> **重要**
>
> このウェブフックリクエストが送信されるには
> `sora.conf` の [ignore_audio_streaming_webhook](SORA_CONF.html#8f086b) が `false` である必要があります。

以下の 2 つの条件で音声ストリーミングが開始した時にウェブフックリクエストを送信します。

- [StartAudioStreaming](API_AUDIO_STREAMING.html#0f1087) API が実行されるか、またはセッションウェブフックで `audio_streaming: true` 払い出されたタイミングでウェブフックリクエストを送信します。
- 音声ストリーミングの [ウェブフック](AUDIO_STREAMING.html#a62cea) を利用時に[SubscribeAudioStreamingResultPush](API_AUDIO_STREAMING.html#d26262) API が呼び出される、もしくはクライアントが接続されて、そのチャネルで音声ストリーミングの結果をサブスクライブしているクライアント数が 1 以上になった場合

[session.created](SESSION_WEBHOOK.html#1d1984) よりも必ず **後に** ウェブフックが送信されます。

* - キー
  - 型
  - 内容
* - type
  - string
  - "audio-streaming.started"
* - id
  - string
  - ウェブフック ID (ユニーク)
* - label
  - string
  - sora.conf の label にて指定した値
* - node_name
  - string
  - Sora ノード名
* - version
  - string
  - Sora のバージョン
* - channel_id
  - string
  - チャネル ID
* - group_id
  - string
  - セッションをグルーピングする ID
* - session_id
  - string
  - セッション ID
* - timestamp
  - string (RFC3339 UTC マイクロ秒)
  - ウェブフック作成時間
* - created_time
  - integer (UNIX 時間)
  - セッション生成時間
* - created_timestamp
  - string (RFC3339 UTC マイクロ秒)
  - セッション生成時間
* - spotlight
  - boolean
  - スポットライトが有効かどうか
* - session_metadata
  - json
  - (値がある場合) session.created の戻り値で指定した値
* - external_signaling_url
  - string
  - (クラスター機能が有効の場合) 接続先の external_signaling_url
* - data
  - object
  - 音声ストリーミング固有

data には以下が含まれます。

* - キー
  - 型
  - 内容
* - audio_streaming_auto
  - boolean
  - 自動開始/停止が有効かどうか
* - audio_streaming_started_timestamp
  - string (RFC3339 UTC マイクロ秒)
  - 音声ストリーミングを開始した時間

```javascript
{
  "channel_id": "sora",
  "created_time": 1638337454,
  "created_timestamp": "2021-12-01T05:44:14.523736Z",
  "id": "2YN2DQE5KD3GSFSVPAYZ7VTAV4",
  "label": "WebRTC SFU Sora",
  "spotlight": false,
  "node_name": "sora@127.0.0.1",
  "group_id": "NPR769YPQ914K10FW42PGH4TKW",
  "session_id": "NPR769YPQ914K10FW42PGH4TKW",
  "timestamp": "2022-12-01T05:44:14.523912Z",
  "type": "audio-streaming.started",
  "version": "2023.2.0",
  "session_metadata": {"spam": "egg"},
  "external_signaling_url": "wss://node-01.example.com/signaling",
  "data": {
    "audio_streaming_auto": true,
    "audio_streaming_started_timestamp": "2021-12-01T05:44:16.523912Z"
  }
}
```


## audio-streaming.stopped

> **重要**
>
> このウェブフックリクエストが送信されるには
> `sora.conf` の [ignore_audio_streaming_webhook](SORA_CONF.html#8f086b) が `false` である必要があります。

以下の 3 つの条件で音声ストリーミングが停止した時にウェブフックリクエストを送信します。

- セッションが破棄された場合
- [StopAudioStreaming](API_AUDIO_STREAMING.html#bad1bb) API が呼び出す場合
- 音声ストリーミングの [ウェブフック](AUDIO_STREAMING.html#a62cea) を利用時に[UnsubscribeAudioStreamingResultPush](API_AUDIO_STREAMING.html#b63656) API が呼び出される、もしくはクライアントが切断されて、そのチャネルで音声ストリーミングの結果をサブスクライブしているクライアント数が 0 になった場合

[session.destroyed](SESSION_WEBHOOK.html#ccb165) よりも必ず **前に** ウェブフックが送信されます。

* - キー
  - 型
  - 内容
* - type
  - string
  - "audio-streaming.started"
* - id
  - string
  - ウェブフック ID (ユニーク)
* - label
  - string
  - sora.conf の label にて指定した値
* - node_name
  - string
  - Sora ノード名
* - version
  - string
  - Sora のバージョン
* - channel_id
  - string
  - チャネル ID
* - group_id
  - string
  - セッションをグルーピングする ID
* - session_id
  - string
  - セッション ID
* - timestamp
  - string (RFC3339 UTC マイクロ秒)
  - ウェブフック作成時間
* - created_time
  - integer (UNIX 時間)
  - セッション生成時間
* - created_timestamp
  - string (RFC3339 UTC マイクロ秒)
  - セッション生成時間
* - spotlight
  - boolean
  - スポットライトが有効かどうか
* - session_metadata
  - json
  - (値がある場合) session.created の戻り値で指定した値
* - external_signaling_url
  - string
  - (クラスター機能が有効の場合) 接続先の external_signaling_url
* - data
  - object
  - 音声ストリーミング固有

data には以下が含まれます。

* - キー
  - 型
  - 内容
* - audio_streaming_auto
  - boolean
  - 自動開始/停止が有効かどうか
* - audio_streaming_started_timestamp
  - string (RFC3339 UTC マイクロ秒)
  - 音声ストリーミングを開始した時間
* - audio_streaming_stopped_timestamp
  - string (RFC3339 UTC マイクロ秒)
  - 音声ストリーミングを停止した時間

```javascript
{
  "channel_id": "sora",
  "created_time": 1638337454,
  "created_timestamp": "2021-12-01T05:44:14.523736Z",
  "id": "2YN2DQE5KD3GSFSVPAYZ7VTAV4",
  "label": "WebRTC SFU Sora",
  "spotlight": false,
  "node_name": "sora@127.0.0.1",
  "group_id": "NPR769YPQ914K10FW42PGH4TKW",
  "session_id": "NPR769YPQ914K10FW42PGH4TKW",
  "timestamp": "2022-12-01T05:44:14.523912Z",
  "type": "audio-streaming.stopped",
  "version": "2023.2.0",
  "session_metadata": {"spam": "egg"},
  "external_signaling_url": "wss://node-01.example.com/signaling",
  "data": {
    "audio_streaming_auto": true,
    "audio_streaming_started_timestamp": "2021-12-01T05:44:16.523912Z",
    "audio_streaming_stopped_timestamp": "2021-12-01T05:44:16.523912Z"
  }
}
```

## API

### [TerminateSession](API_SESSION.html#ba022b) API

セッションを強制的に終了させる API です。

`channel_id` を指定して、セッションを終了させます。
`session_id` を追加で指定することができますが、 `session_id` が見つからない場合はエラーになります。

API 実行中に新規の接続が来た場合、その接続はいったん保留して、セッション破棄後に新規セッションでの接続として扱います。

## クラスター時の挙動について

### セッションが存在していたノードで障害が起きた場合でも、 `session.destroyed` は送信されます

別のノードが `session.destroyed` を送信します。その際 `reason` 項目には `abort` が入ります。

`reason` が `abort` の場合、以下の情報が **最新の状態** ではない場合があります。

- `total_connections`
- `max_connections`

### セッションが存在していたノードで障害が起きた際、同一ウェブフックが複数回送られる事があります

以下のウェブフックは複数回送られる可能性はあります、その際はウェブフックの `"id"` は同一になります。

- `session.destroyed`
- `recording.started`
- `recording.report`
- `audio-streaming.stopped`

## クラスターリレー時の挙動について

### セッションウェブフックはどのノードが送信しますか？

`session.created` を送信したノードが `session.destroyed` を送信します。

## HTTP ヘッダー

> **警告**
>
> この機能は [実験的機能](EXPERIMENTAL.html) のため、正式版では仕様が変更される可能性があります

> **注釈**
>
> JSON のパース時の判断などに利用してください。

### sora-session-webhook-type

> **注意**
>
> `x-sora-session-webhook-type` は非推奨です、 `sora-session-webhook-type` を利用してください

セッションウェブフックの HTTP ヘッダー に `sora-session-webhook-type` または `x-sora-session-webhook-type` というヘッダー名でセッションウェブフックのタイプが入ってきます。

`type` が `session.created` の場合は `sora-session-webhook-type: session.created` または `x-sora-session-webhook-type: session.created` のように値が入ってきます。

### sora-session-id

セッションウェブフックの HTTP ヘッダー に `sora-session-id` というヘッダー名でセッション ID が入ってきます。

セッション ID が `46NNAV9S0X3TD778A1JBYYCBS8` の場合は `sora-session-id: 46NNAV9S0X3TD778A1JBYYCBS8` のように値が入ってきます。

## シーケンス図

```mermaid
sequenceDiagram
    autonumber
    participant C1 as クライアント1
    participant C2 as クライアント2
    participant S as Sora
    participant A as アプリケーションサーバー
    C1->>S: type: connect
    S->>+A: 認証ウェブフック
    A-->>-S: 200 OK
    S->>+A: セッションウェブフック<br/>type: session.created
    A-->>-S: 200 OK
    S->>C1: type: offer
    C1->>S: type: answer
    Note over C1,A: クライアント1 WebRTC 確立
    S->>+A: イベントウェブフック<br/>type: connection.created
    A-->>-S: 200 OK
    C2->>S: type: connect
    S->>+A: 認証ウェブフック
    A-->>-S: 200 OK
    S->>C2: type: offer
    C2->>S: type: answer
    Note over C1,A: クライアント2 WebRTC 確立
    S->>+A: イベントウェブフック<br/>type: connection.created
    A-->>-S: 200 OK
    C1->>S: "type": "disconnect"
    S->>+A: イベントウェブフック<br/>type: connection.destroyed
    A-->>-S: 200 OK
    S->>C1: WebSocket Close
    Note over C1,A: クライアント1 WebRTC 切断
    C2->>S: "type": "disconnect"
    S->>+A: イベントウェブフック<br/>type: connection.destroyed
    A-->>-S: 200 OK
    S->>C2: WebSocket Close
    Note over C1,A: クライアント2 WebRTC 切断
    note right of S: 接続数 0 から 15 秒経過
    S->>+A: セッションウェブフック<br/>type: session.destroyed
    A-->>-S: 200 OK
```
