# ウェブフック

## 概要

Sora Cloud では Sora のウェブフックを利用できます。指定した URL にウェブフックリクエストを送信します。

## 制限

### 1 プロジェクト 1 ウェブフック URL

ウェブフックの通知先 URL は 1 プロジェクト当たり、それぞれのウェブフックにつき 1 つずつです。

### 利用できるウェブフック送信先のポート番号

- ウェブフック送信先が HTTP の場合に利用できるポート番号は 80 番のみ
- ウェブフック送信先が HTTPS の場合に利用できるポート番号は 443 番のみ

## タイムアウト

ウェブフックのタイムアウトは 10 秒です。これを超えた場合は送信側から切断を行います。
タイムアウトが頻発する場合は一時的にウェブフックでの送信を停止する場合があります。

タイムアウトした場合は認証処理はエラーとして返します。

## Sora 認証ウェブフック

[認証ウェブフック](https://sora-doc.shiguredo.jp/AUTH_WEBHOOK)

- 戻りのステータスコードは `200 OK` を期待します

### 払い出し可能な項目

> **注意**
>
> プロジェクトにオブジェクトストレージ設定が行われていない場合、または録画機能の [制限](recording.html#4a8cb2) に該当する指定を行っていた場合、録画は開始されません。

- client_id
- bundle_id
- audio
- audio_codec_type
- audio_bit_rate
- audio_opus_params
- audio_lyra_params
- video
- video_codec_type
- video_bit_rate
- data_channel_signaling
- ignore_disconnect_websocket
- ignore_max_connections
- data_channels
- metadata
- event_metadata
- recording
- recording_expire_time
- recording_split_only
- recording_split_duration
- recording_metadata
- recording_format
- signaling_notify
- signaling_notify_metadata
- signaling_notify_ice_connection_state
- simulcast
- simulcast_auto_rids
- simulcast_rpc_rids
- simulcast_request_rid
- simulcast_rid- 2027 年 12 月に廃止します
  - 代わりに `simulcast_request_rid` を利用してください
- simulcast_encodings- AV1 でサイマルキャストを利用した録画をする場合は `scalabilityMode` に `L1T1` を設定してください
- simulcast_multicodec
- simulcast_codecs
- spotlight
- spotlight_number- **この払い出しは非推奨です**
  - Sora セッションウェブフックの `"type": "session.created"` の戻り値で `spotlight_number` を払い出してください
- spotlight_encodings- AV1 でスポットライトを利用した録画をする場合は `scalabilityMode` に `L1T1` を設定してください
- rtc_stats
- video_vp9_params- profile_id
- video_av1_params- profile
  - level_idx
  - tier
- video_h264_params- profile_level_id
  - b_frame
- video_h265_params- level_id
  - b_frame
  - profile_id
  - tier_flag
  - tx_mode
- forwarding_filters
- recording_block
- turn_tcp_only
- turn_tls_only
- connection_lifetime
- playout_delay_min_delay
- playout_delay_max_delay
- cluster_affinity
- ice_connection_state_disconnected_timeout_ms
- ice_connection_state_failed_timeout_ms
- static_ip_address- [固定 IP アドレス接続機能（オプション）](static_ip.html) をご契約中のお客様のみ利用できます

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

[セッションウェブフック](https://sora-doc.shiguredo.jp/SESSION_WEBHOOK)

戻りのステータスコードは `200 OK` を期待します。

### 払い出し可能な項目

> **注意**
>
> プロジェクトにオブジェクトストレージ設定が行われていない場合、または録画機能の [制限](recording.html#4a8cb2) に該当する指定を行っていた場合、録画は開始されません。

セッションウェブフックの `"type": "session.created"` の戻り値で以下の項目を払い出すことが可能です。

- session_metadata
- session_lifetime
- forwarding_filters
- spotlight_number
- max_connections
- duplicate_client_id
- recording
- recording_metadata
- recording_expire_time
- recording_split_duration
- recording_split_only
- recording_format

## Sora イベントウェブフック

[イベントウェブフック](https://sora-doc.shiguredo.jp/EVENT_WEBHOOK)

- 戻りのステータスコードは `200 OK` を期待します

## Sora 統計ウェブフック

[統計ウェブフック](https://sora-doc.shiguredo.jp/STATS_WEBHOOK)

- 戻りのステータスコードは `200 OK` を期待します

## Sora Cloud ウェブフック

### 録画ファイルアップロード時のウェブフック送信

[録画ファイルアップロード時のウェブフック送信](recording.html#e46279)

- 戻りのステータスコードは `200 OK` を期待します

#### recording-report.uploaded

Sora Cloud が録画レポートファイルのアップロード完了時に通知します。

#### archive.uploaded

Sora Cloud が一括録画時の録画ファイルとメタデータファイルのアップロード完了時に通知します。

#### split-archive.uploaded

Sora Cloud が分割録画時の分割録画ファイルとメタデータファイルのアップロード完了時に通知します。

#### split-archive-end.uploaded

Sora Cloud が分割録画時の録画終了ファイルのアップロード完了時に通知します。

### Hisui Cloud のウェブフック通知

[Hisui Cloud のウェブフック送信](hisui_cloud.html#a62cea)

- 戻りのステータスコードは `200 OK` を期待します

#### hisui-cloud-job.started

録画合成開始時に通知します。

#### hisui-cloud-job.composited

録画合成成功時に通知します。

#### hisui-cloud-job.uploaded

録画合成ファイルのアップロード成功時に通知します。

#### hisui-cloud-job.completed

録画合成処理が完了時に通知します。

#### hisui-cloud-job.canceled

処理キャンセル時に通知します。

#### hisui-cloud-job.failed

処理失敗時に通知します。






## ウェブフックの IP アドレス

Sora Cloud のウェブフックは [Cloudflare Workers](https://workers.cloudflare.com/) を利用して実現しています。

そのため [Cloudflare の IP アドレス範囲](https://www.cloudflare.com/ips/) からウェブフックを通知します。


## 署名

Sora Cloud が通知する Sora のウェブフックの HTTP ヘッダーにはそのウェブフックの署名が付与されています。

HTTP ヘッダー名 `sora-cloud-signature` にそのウェブフックの生成時間と JSON ペイロードに対する署名が入っています。

### 署名の検証

**t**: タイムスタンプ
**v1**: 署名

```
sora-cloud-signature:
t=1692774577,
v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```

### 署名ペイロード

> **重要**
>
> 署名には必ずプライマリー API キーが利用されます。

- タイムスタンプ (文字列)
- 文字 .
- JSON ペイロード (リクエストボディ)

署名疑似コード:

```
signature = hmac(sha256, primary_api_key, "<Timestamp>.<JSON>").hexdigest()
```

signature と v1 の値を比較することで署名の検証が可能になります。

タイムスタンプは Unix time を利用しています。

## 署名検証サンプルコード

### Python

```python
import hashlib
import hmac
import http.server
import json
import socketserver

# プロジェクトのプライマリー API キーを指定してください
API_KEY = 'api-key'
PORT = 8000

class AuthWebhookResponder(http.server.BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers['content-length'])
        sora_cloud_sig = self.headers['sora-cloud-signature']
        json_string = self.rfile.read(content_length).decode('utf-8')
        body = json.loads(json_string)

        print(sora_cloud_sig)
        print(json_string)
        print(json.dumps(body, indent=2))

        verify_signature(API_KEY, sora_cloud_sig, json_string)

        # 認証を許可する JSON を返す
        allowed = {"allowed": True}
        s = json.dumps(allowed)

        self.send_response(200)
        self.send_header('Content-Length', len(s))
        self.end_headers()
        self.wfile.write(s.encode())

def verify_signature(api_key, sora_cloud_signature_header_value, request_json_string):
    # sora-cloud-signature ヘッダーの値からタイムスタンプと署名を取り出す
    splited = sora_cloud_signature_header_value.split(',')
    signagure_timestamp = splited[0].split('=')[1]
    header_signature_value = splited[1].split('=')[1]

    # 署名する文字列を生成する
    # <タイムスタンプ>.<リクエストJSON>
    msg = f'{signagure_timestamp}.{request_json_string}'

    # 署名する
    signature = hmac \
        .new(
            key=bytes(api_key,'utf-8'),
            msg=bytes(msg, 'utf-8'),
            digestmod=hashlib.sha256) \
        .hexdigest()

    # ヘッダーの値と署名した値が一致することを確認する
    assert(header_signature_value == signature)

with socketserver.TCPServer(("", PORT), AuthWebhookResponder) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()
```











## ローカル開発環境へウェブフックを送信する

ローカルの開発環境に Sora Cloud からウェブフックを直接送信することは、ネットワーク上の制約によりできません。
そのため ngrok の利用をおすすめします。 ngrok は無料で使い始められます。

### ngrok を利用する

**URL**: 

ngrok アカウントが必要です。
また、固定したエンドポイント URL を利用するには、
Pro プラン ($20/月) 以上のプランに入る必要があります。



## シーケンス図

### Sora 関連ウェブフック

- Sora 認証ウェブフック
- Sora セッションウェブフック- session.created
  - session.destroyed
- Sora イベントウェブフック- connection.created
  - connection.updated
  - connection.destroyed

```mermaid
sequenceDiagram
    participant client as クライアント
    participant sora as WebRTC SFU Sora
    participant cloud as Sora Cloud
    participant app as アプリケーションサーバー
    client->>sora: "type": "connect"
    sora->>+cloud: 認証ウェブフック
    note over cloud: アクセストークンを確認
    cloud->>+app: 認証ウェブフック
    app-->>-cloud: 200 OK<br>"allowed": true
    cloud-->>-sora: 200 OK<br>"allowed": true
    sora->>client: "type": "offer"
    client->>sora: "type": "answer"
    note over client,sora: WebRTC 確立
    sora->>+cloud: セッションウェブフック<br>"type": "session.created"
    cloud->>+app: セッションウェブフック<br>"type": "session.created"
    app-->>-cloud: 200 OK
    cloud-->>-sora: 200 OK
    sora->>+cloud: イベントウェブフック<br>"type": "connection.created"
    cloud->>+app: イベントウェブフック<br>"type": "connection.created"
    app-->>-cloud: 200 OK
    cloud-->>-sora: 200 OK
    note over client,sora: 1 分経過
    sora->>+cloud: イベントウェブフック<br>"type": "connection.updated"
    cloud->>+app: イベントウェブフック<br>"type": "connection.updated"
    app-->>-cloud: 200 OK
    cloud-->>-sora: 200 OK
    client->>sora: "type": "disconnect"
    sora->>+cloud: イベントウェブフック<br>"type": "connection.destroyed"
    cloud->>+app: イベントウェブフック<br>"type": "connection.destroyed"
    app-->>-cloud: 200 OK
    cloud-->>-sora: 200 OK
    sora->>client: シグナリング切断
```

### Sora Cloud 関連ウェブフック

- Sora イベントウェブフック- recording.started
  - recording.report
  - archive.started
  - archive.available
- Sora Cloud ウェブフック- archive.uploaded
  - recording-report.uploaded

```mermaid
sequenceDiagram
    autonumber
    participant client as クライアント
    participant sora as WebRTC SFU Sora
    participant cloud as Sora Cloud
    participant app as アプリケーションサーバー
    participant s3 as オブジェクトストレージ
    note over client,sora: WebRTC 確立
    app->>+cloud: StartRecording API
    cloud->>+sora: StartRecording API
    sora-->>-cloud: 200 OK
    cloud-->>-app: 200 OK
    note over sora: 録画開始
    sora->>+cloud: イベントウェブフック<br>"type": "recording.started"
    cloud->>+app: イベントウェブフック<br>"type": "recording.started"
    app-->>-cloud: 200 OK
    cloud-->>-sora: 200 OK
    app->>+cloud: StopRecording API
    cloud->>+sora: StopRecording API
    sora-->>-cloud: 200 OK
    cloud-->>-app: 200 OK
    note over sora: 録画終了
    par
        sora->>+cloud: イベントウェブフック<br>"type": "archive.available"
        cloud->>+app: イベントウェブフック<br>"type": "archive.available"
        app-->>-cloud: 200 OK
        cloud-->>-sora: 200 OK
        sora->>s3: 録画ファイルアップロード
        cloud->>+app: Sora Cloud ウェブフック<br>"type": "archive.uploaded"
        app-->>-cloud: 200 OK
    and
        sora->>+cloud: イベントウェブフック<br>"type": "recording.report"
        cloud->>+app: イベントウェブフック<br>"type": "recording.report"
        app-->>-cloud: 200 OK
        cloud-->>-sora: 200 OK
        sora->>s3: 録画レポートアップロード
        cloud->>+app: Sora Cloud ウェブフック<br>"type": "recording-report.uploaded"
        app-->>-cloud: 200 OK
    end
```
