mcpField
T
twilio-video

twilio-video

github.com/alphaonedev開発45 回インストール
リポジトリ

twilio-video について

twilio-videoは、Twilio Videoを使った本番WebRTCシステムの構築・運用ガイドです。開発者向けに、ルーム、トークン、メディアトラック、録画、ネットワーク品質、運用方法を扱います。

主な機能

  1. 01ルームのライフサイクル管理
  2. 02Video Access Tokenの発行
  3. 03メディアトラックの公開・購読・管理
  4. 04Compositionsによる録画
  5. 05ネットワーク品質と帯域幅の設定
  6. 06Webhookとコールバックの処理

活用シーン

  1. 01ビデオ会議ルームの作成と終了
  2. 02ユーザーへのルームアクセストークン発行
  3. 03参加者の管理とモデレーション
  4. 04録画生成とコールバック処理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/alphaonedev/openclaw-graph --skill twilio-video

インストール後、Claude Code で/twilio-videoを入力して読み込まれたか確認します。

詳細

twilio-video

目的

OpenClaw で、本番環境の Twilio Video を設計、実装、運用、トラブルシューティングできるようにします。

  • ライフサイクル制御を含め、ビデオルーム(グループおよびピアツーピア)を作成、管理します。
  • ビデオ権限(ID、ルームのスコープ、TTL、キーのローテーション)を含むアクセストークンを発行します。
  • トラック(音声/ビデオ/データ)の公開とサブスクライブ、トラックの優先度管理、モデレーション(ミュート/公開停止/参加者の削除)を実施します。
  • コンポジション(サーバーサイドの録画出力)による録画を有効化、運用し、コールバックとストレージパイプラインも扱います。
  • ネットワーク品質 API と帯域幅プロファイルを使用して、実環境のネットワーク制約下で通話品質を維持します。
  • Webhook、認証、レート制限、コスト管理、運用手順書など、Twilio のクラスターのパターンに Video を統合します。

このガイドでは、コンプライアンス、可観測性、インシデント対応の要件を満たす、マルチテナントの本番環境向け WebRTC システムを構築することを前提としています。


前提条件

Twilio アカウントと認証情報
  • Twilio アカウント SID(形式は AC...)
  • Twilio API Key SID(形式は SK...)と Secret
  • (任意)レガシー/基本認証用の Twilio Auth Token(形式は ...)。サーバー間通信では API Key と Secret を推奨します。
  • Twilio Video が有効なプロジェクト(ほとんどのアカウントでデフォルト)

API Key を作成します。

twilio api:core:keys:create --friendly-name "video-prod-key-2026-02"
対応 SDK/ライブラリ(バージョンを固定)

サーバーサイド(トークンの発行、REST API 呼び出し):

  • Node.js:node >= 20.11.1(LTS)、npm >= 10.2.4
    • [email protected](Twilio Node ヘルパーライブラリ)
    • [email protected](カスタム JWT 処理を実装する場合。Twilio ヘルパーでもトークンを発行できます)
  • Python:python >= 3.11.7
    • twilio==9.4.1
  • Go:go >= 1.22.1

クライアントサイド:

  • Twilio Video JS SDK:[email protected]
    • ブラウザーの対応状況:最新の Chrome/Edge、Firefox ESR、Safari 17 以降(macOS/iOS の制約が適用されます)
  • iOS:TwilioVideo iOS SDK 5.10.0(CocoaPods/SPM)
  • Android:Twilio Video Android SDK 7.6.0(Gradle)

Twilio CLI:

  • [email protected](Node ベース)
  • プラグイン:
    • @twilio-labs/[email protected](Video コマンド。プラグインの利用可否は環境によって異なるため、ご利用の環境で確認してください)

Twilio CLI をインストールします。

npm i -g [email protected]
twilio --version

プラグインをインストールします。

twilio plugins:install @twilio-labs/[email protected]
twilio plugins
認証の設定(ローカル開発と CI)

環境変数を推奨します。

  • TWILIO_ACCOUNT_SID
  • TWILIO_API_KEY
  • TWILIO_API_SECRET
  • (任意)TWILIO_AUTH_TOKEN(可能であれば CI では使用しないでください)

例(bash):

export TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
export TWILIO_API_KEY="YOUR_API_KEY_SID"
export TWILIO_API_SECRET="your_api_secret_from_console"

Twilio CLI でログインします(認証情報はローカルに保存されるため、CI ランナーでは推奨しません)。

twilio login
ネットワークとインフラの前提条件
  • Webhook 用のパブリック HTTPS エンドポイント(Composition コールバック、ステータスコールバック)。
  • TLS:最新の暗号方式を使用し、信頼できる CA の証明書を使用してください(Let’s Encrypt も使用できます)。
  • 時刻同期:JWT を発行するサーバーで NTP を有効にしてください(時刻のずれによりトークンが無効になります)。
  • TURN/STUN:Twilio が提供します。アウトバウンド UDP/TCP 3478/5349 を許可してください。エンタープライズネットワークでは TCP/TLS フォールバックが必要になる場合があります。

コアコンセプト

ルーム:グループとピアツーピア(P2P)
  • グループルーム:メディアは Twilio の SFU 経由でルーティングされます。より大きなルーム、録画/コンポジション、帯域幅プロファイル、ネットワーク品質、発言者の特定などに対応します。ほとんどの本番環境でデフォルトとして使用します。
  • ピアツーピアルーム:参加者間で直接メッシュ接続します。スケーラビリティに制約があり、サーバーサイド機能も少ないため、厳しい遅延要件がある 1 対 1 の用途を除き、本番環境では一般に推奨されません。

主なプロパティ:

  • type:group | group-small | peer-to-peer(利用可否はアカウント/リージョンによって異なります)
  • status:in-progress | completed
  • maxParticipants:コストと品質を管理するための上限を設定します
参加者とトラック
  • 参加者:ルーム内の ID(ルームごとに一意)。
  • トラック:
    • 音声トラック
    • ビデオトラック
    • データトラック(信頼性のある/ないメッセージング)
  • トラックのライフサイクル:published → subscribed(リモート)→ unpublished/stopped
  • サーバーサイドのモデレーションでは、一般に次の操作を行います。
    • 参加者を切断する
    • 公開を強制的に停止する(対応している場合)
    • シグナリングとポリシーを通じてクライアントの動作を制御する
アクセストークン(JWT)

Twilio Videoでは、VideoGrantを含むJWTアクセストークンを使用します。

  • identity:安定したユーザー識別子(テナントスコープ)
  • ttl:秒単位。短く設定し(例:3600)、更新してください
  • room:任意。トークンを特定のルームに制限します
  • API Key Secretで署名します

クロックのずれはよくある障害原因です。NTPを使用してください。

Compositionsによる録画

本番環境でのTwilio Videoの「録画」は、通常 Compositions を意味します。

  • Compositionは、録画されたトラックから作成されるサーバー側レンダリングの出力(例:MP4)です。
  • 次の項目を設定できます。
    • レイアウト(グリッド、アクティブスピーカー、カスタム)
    • 形式
    • ステータスコールバックURL
  • 非同期の完了処理とストレージ(S3/GCS/Azure)は、独自に処理する必要があります。
ネットワーク品質API
  • 参加者ごとのネットワーク品質レベル(0~5)と統計情報を提供します。
  • 次の用途に使用します。
    • ビットレート/解像度の調整
    • UI警告の表示
    • 音声のみへの切り替え判断
  • 帯域幅プロファイルと組み合わせることで、予測しやすい動作を実現できます。
帯域幅プロファイルとトラックの優先度
  • 帯域幅プロファイルは、Twilioがトラック間で帯域幅をどのように割り当てるかを定義します。
  • トラックの優先度(low/standard/high)によって、どのトラックが先に品質を低下させるかが決まります。
  • 次の用途に使用します。
    • プレゼンターのビデオを優先する
    • 混雑時も音声を安定させる
Webhookとコールバック

一般的なコールバックの種類:

  • Compositionのステータスコールバック
  • ルームのステータスコールバック(設定されている場合)
  • 参加者イベント(多くの場合、クライアント側で処理します。サーバーからREST APIをポーリングすることもできます)

Webhookは次の要件を満たす必要があります。

  • HTTPSを使用する
  • 検証する(Twilioの署名検証)
  • 冪等である(Twilioは再試行します)

インストールとセットアップ

公式Python SDK — Video

リポジトリ: https://github.com/twilio/twilio-python
PyPI: pip install twilio · サポート対象: Python 3.7~3.13

from twilio.rest import Client
from twilio.jwt.access_token import AccessToken
from twilio.jwt.access_token.grants import VideoGrant

client = Client()

# Create a room
room = client.video.v1.rooms.create(
    unique_name="DailyStandup",
    type="go"  # or 'group' / 'peer-to-peer'
)
print(room.sid)

# Generate participant access token
token = AccessToken(
    os.environ["TWILIO_ACCOUNT_SID"],
    os.environ["TWILIO_API_KEY"],
    os.environ["TWILIO_API_SECRET"],
    identity="alice"
)
token.add_grant(VideoGrant(room="DailyStandup"))
print(token.to_jwt())

ソース:twilio/twilio-python — video

Ubuntu 22.04 LTS(x86_64)
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg jq
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v
npm -v

sudo npm i -g [email protected]
twilio --version
twilio plugins:install @twilio-labs/[email protected]
Fedora 39/40(x86_64)
sudo dnf install -y nodejs npm jq
node -v
npm -v

sudo npm i -g [email protected]
twilio plugins:install @twilio-labs/[email protected]
macOS(Intel + Apple Silicon)

Homebrewを使用する場合:

brew install node@20 jq
echo 'export PATH="/opt/homebrew/opt/node@20/bin:$PATH"' >> ~/.zshrc  # Apple Silicon
echo 'export PATH="/usr/local/opt/node@20/bin:$PATH"' >> ~/.zshrc     # Intel
source ~/.zshrc

npm i -g [email protected]
twilio plugins:install @twilio-labs/[email protected]
Windows 11(PowerShell)

公式インストーラーまたはwingetからNode.js 20 LTSをインストールします。

winget install OpenJS.NodeJS.LTS
node -v
npm -v
npm i -g [email protected]
twilio --version
twilio plugins:install @twilio-labs/[email protected]
サーバーライブラリのセットアップ(Node.js)
mkdir -p video-service && cd video-service
npm init -y
npm i [email protected] [email protected] @fastify/[email protected] [email protected]
npm i -D [email protected] [email protected] @types/[email protected]

最小限の tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
サーバーライブラリのセットアップ(Python)
python3.11 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip==24.0
pip install twilio==9.4.1 fastapi==0.109.2 uvicorn==0.27.1 python-dotenv==1.0.1

主な機能

ルームのライフサイクル管理

本番環境でのパターン:

  • ルームを必要に応じて作成します(または、予定されているセッション用に事前にプロビジョニングします)。
  • uniqueName の命名規則を徹底します。テナントとリソースIDを含めてください。
  • コストを管理し、不正使用を防ぐために maxParticipants を設定します。
  • セッション終了時にルームを明示的に完了し、課金を停止します。

命名規則の例:

  • acme-prod:class:9f3c2b1a
  • tenantId:resourceType:resourceId

Node:ルームを作成します。

import twilio from "twilio";

const client = twilio(process.env.TWILIO_API_KEY!, process.env.TWILIO_API_SECRET!, {
  accountSid: process.env.TWILIO_ACCOUNT_SID!,
});

async function createRoom() {
  const room = await client.video.v1.rooms.create({
    uniqueName: "acme-prod:class:9f3c2b1a",
    type: "group",
    maxParticipants: 25,
    recordParticipantsOnConnect: false
  });
  return room;
}

ルームを完了します。

await client.video.v1.rooms("RM0123456789abcdef0123456789abcdef")
  .update({ status: "completed" });
アクセストークンの発行(VideoGrant)

トークンサービスの要件:

  • 呼び出し元を認証します(Twilioの認証ではなく、自社の認証を使用します)。
  • ルームへのアクセスを認可します(テナントとロールを確認します)。
  • 短期間有効なトークンを発行します(例:15~60分)。
  • ダウンタイムなしでAPIキーをローテーションできるようにします(複数の署名キーをサポートします)。

Nodeでのトークン発行:

import twilio from "twilio";

const { AccessToken } = twilio.jwt;
const { VideoGrant } = AccessToken;

export function mintVideoToken(params: {
  identity: string;
  room?: string;
  ttlSeconds?: number;
}) {
  const token = new AccessToken(
    process.env.TWILIO_ACCOUNT_SID!,
    process.env.TWILIO_API_KEY!,
    process.env.TWILIO_API_SECRET!,
    {
      identity: params.identity,
      ttl: params.ttlSeconds ?? 3600
    }
  );

  token.addGrant(new VideoGrant({ room: params.room }));
  return token.toJwt();
}

Pythonでのトークン発行:

from twilio.jwt.access_token import AccessToken
from twilio.jwt.access_token.grants import VideoGrant

def mint_video_token(identity: str, room: str | None = None, ttl: int = 3600) -> str:
    token = AccessToken(
        account_sid=os.environ["TWILIO_ACCOUNT_SID"],
        signing_key_sid=os.environ["TWILIO_API_KEY"],
        secret=os.environ["TWILIO_API_SECRET"],
        identity=identity,
        ttl=ttl,
    )
    token.add_grant(VideoGrant(room=room))
    return token.to_jwt()
トラックの公開、サブスクリプション、モデレーション

クライアント側(JS)で、帯域幅プロファイルと最も音量の大きいスピーカーを指定して接続します。

import Video from "twilio-video";

const room = await Video.connect(token, {
  name: "acme-prod:class:9f3c2b1a",
  dominantSpeaker: true,
  networkQuality: { local: 1, remote: 1 },
  bandwidthProfile: {
    video: {
      mode: "collaboration",
      dominantSpeakerPriority: "high",
      maxSubscriptionBitrate: 2500000,
      renderDimensions: {
        high: { width: 1280, height: 720 },
        standard: { width: 640, height: 360 },
        low: { width: 320, height: 180 }
      }
    }
  }
});

モデレーションのパターン:

  • 「ミュート」は通常、クライアントポリシー(ローカルトラックの無効化)とサーバー側のロール制御によって実施します。
  • 強制的に適用するには、参加者を切断します。
await client.video.v1.rooms("RM...").participants("PA...")
  .update({ status: "disconnected" });
録画とCompositions

一般的なフロー:

  1. 必要に応じて参加者の録画を有効にするか、Compositionのソースに依存します。
  2. ルームの完了時(または必要に応じて)にCompositionを作成します。
  3. ステータスコールバック(processing → completed/failed)を受け取ります。
  4. CompositionのメディアURLを取得し、ストレージに取り込みます。

Compositionを作成します(Node)。

const composition = await client.video.v1.compositions.create({
  roomSid: "RM0123456789abcdef0123456789abcdef",
  audioSources: "*",
  videoLayout: {
    grid: { video_sources: ["*"] }
  },
  format: "mp4",
  statusCallback: "https://video.acme.com/twilio/composition-status",
  statusCallbackMethod: "POST"
});

Compositionのメディアを取得します。

const c = await client.video.v1.compositions("CJ0123456789abcdef0123456789abcdef").fetch();
console.log(c.links?.media);
ネットワーク品質APIの使用

クライアント側のイベント処理:

room.on("networkQualityLevelChanged", (level, stats) => {
  // level: 0..5
  // stats: { audio: { send, recv }, video: { send, recv } } depending on SDK
  if (level <= 2) {
    console.warn("Network degraded", level, stats);
  }
});

サーバー側:参加者のネットワーク品質を取得します(REST APIのフィールドでサポートされている場合。それ以外の場合はクライアントのテレメトリを使用します)。

帯域幅とコーデックの調整

本番環境でのデフォルト設定:

  • 幅広い互換性のためにVP8を優先します。Safariの利用者が多い場合はH.264を検討してください。
  • maxSubscriptionBitrateを使用して、下りの使用量を制限します。
  • 発表者にはトラックの優先度を設定します。
const videoTrack = Array.from(room.localParticipant.videoTracks.values())[0]?.track;
videoTrack?.setPriority("high");
Webhookの処理(Compositionコールバック)

要件:

  • Twilioの署名(X-Twilio-Signature)を検証します。
  • 冪等性:CompositionSidとStatusで重複を排除します。
  • 再試行に対応:Twilioは2xx以外の応答で再試行します。

Fastifyの例:

import Fastify from "fastify";
import twilio from "twilio";

const app = Fastify({ logger: true });

app.post("/twilio/composition-status", async (req, reply) => {
  const signature = req.headers["x-twilio-signature"] as string | undefined;
  const url = "https://video.acme.com/twilio/composition-status";

  const isValid = twilio.validateRequest(
    process.env.TWILIO_AUTH_TOKEN!, // signature validation uses Auth Token
    signature ?? "",
    url,
    req.body as Record<string, string>
  );

  if (!isValid) return reply.code(403).send({ error: "invalid signature" });

  // process CompositionSid, Status, RoomSid, etc.
  return reply.code(204).send();
});

app.listen({ port: 3000, host: "0.0.0.0" });

注:署名の検証にはAPI Secretではなく、Auth Tokenを使用します。サービスでAuth Tokenを使わない場合は、Webhookの検証を、シークレットへのアクセスを制限した小規模なコンポーネントに分離してください。


コマンドリファレンス

注:

  • Twilio CLIコマンドを利用できるかどうかは、インストールされているプラグインとTwilio CLIのバージョンによって異なります。
  • Videoでは、ヘルパーライブラリを使ってREST API経由で操作するのが最も簡単な場合が多く、CLIは確認に便利です。
Twilio CLI:グローバルフラグ

コマンド共通の項目:

  • -o, --output [json|tsv|table] 出力形式
  • --properties <props> 表示するプロパティ(カンマ区切り)
  • --no-header ヘッダーを省略します(tsv/table)。
  • --profile <name> ~/.twilio-cli/config.jsonの名前付きプロファイルを使用します。
  • --log-level [debug|info|warn|error]

例:

twilio api:video:v1:rooms:list -o json --log-level debug
ルーム(CLI経由のREST)

ルームを一覧表示します:

twilio api:video:v1:rooms:list \
  --status in-progress \
  --limit 50 \
  -o table \
  --properties sid,uniqueName,status,type,maxParticipants,dateCreated

関連するフラグ:

  • --status <in-progress|completed>
  • --unique-name <string>(フィルター。CLIジェネレーターが対応している場合)
  • --limit <int>

ルームを取得します:

twilio api:video:v1:rooms:fetch --sid RM0123456789abcdef0123456789abcdef -o json

ルームを作成します:

twilio api:video:v1:rooms:create \
  --unique-name "acme-prod:class:9f3c2b1a" \
  --type group \
  --max-participants 25 \
  --record-participants-on-connect false \
  -o json

ルームを更新します(完了):

twilio api:video:v1:rooms:update \
  --sid RM0123456789abcdef0123456789abcdef \
  --status completed \
  -o json
参加者

ルーム内の参加者を一覧表示します:

twilio api:video:v1:rooms:participants:list \
  --room-sid RM0123456789abcdef0123456789abcdef \
  --status connected \
  --limit 200 \
  -o table \
  --properties sid,identity,status,dateCreated

フラグ:

  • --room-sid <RM...>
  • --status <connected|disconnected>
  • --identity <string>(対応している場合)
  • --limit <int>

参加者を切断します:

twilio api:video:v1:rooms:participants:update \
  --room-sid RM0123456789abcdef0123456789abcdef \
  --sid PA0123456789abcdef0123456789abcdef \
  --status disconnected \
  -o json
Composition

Compositionを作成します:

twilio api:video:v1:compositions:create \
  --room-sid RM0123456789abcdef0123456789abcdef \
  --audio-sources "*" \
  --format mp4 \
  --status-callback "https://video.acme.com/twilio/composition-status" \
  --status-callback-method POST \
  -o json

Compositionを取得します:

twilio api:video:v1:compositions:fetch \
  --sid CJ0123456789abcdef0123456789abcdef \
  -o json

Compositionを一覧表示します:

twilio api:video:v1:compositions:list \
  --room-sid RM0123456789abcdef0123456789abcdef \
  --limit 20 \
  -o table \
  --properties sid,status,format,dateCreated
レコーディング(アカウントや機能で利用できる場合)

ルームのレコーディングを一覧表示します:

twilio api:video:v1:recordings:list \
  --grouping-sid RM0123456789abcdef0123456789abcdef \
  --limit 50 \
  -o table \
  --properties sid,status,trackName,dateCreated
APIキー(ローテーション対応)

キーを作成します:

twilio api:core:keys:create --friendly-name "video-key-rotation-2026-02" -o json

キーを一覧表示します:

twilio api:core:keys:list -o table --properties sid,friendlyName,dateCreated

キーを無効化します:

twilio api:core:keys:remove --sid YOUR_API_KEY_SID

設定リファレンス

1) OpenClawサービスの環境ファイル

パス(Linuxのsystemdパターン):

  • /etc/openclaw/video-service.env

例:

TWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_API_KEY=YOUR_API_KEY_SID
TWILIO_API_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

VIDEO_TOKEN_TTL_SECONDS=3600
VIDEO_ROOM_TYPE=group
VIDEO_MAX_PARTICIPANTS=25

PUBLIC_BASE_URL=https://video.acme.com
COMPOSITION_STATUS_CALLBACK_URL=https://video.acme.com/twilio/composition-status

LOG_LEVEL=info
2) systemdユニット

パス:

  • /etc/systemd/system/openclaw-video.service
[Unit]
Description=OpenClaw Twilio Video Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=/etc/openclaw/video-service.env
WorkingDirectory=/opt/openclaw/video-service
ExecStart=/usr/bin/node /opt/openclaw/video-service/dist/server.js
Restart=on-failure
RestartSec=2
User=openclaw
Group=openclaw
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/openclaw-video
AmbientCapabilities=
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true

[Install]
WantedBy=multi-user.target
3) NGINXリバースプロキシ(TLS + webhookパス)

パス:

  • /etc/nginx/sites-available/video.acme.com
  • /etc/nginx/sites-enabled/video.acme.comへのシンボリックリンク
server {
  listen 443 ssl http2;
  server_name video.acme.com;

  ssl_certificate     /etc/letsencrypt/live/video.acme.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/video.acme.com/privkey.pem;

  client_max_body_size 2m;

  location /twilio/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_read_timeout 30s;
  }

  location /healthz {
    proxy_pass http://127.0.0.1:3000/healthz;
  }
}
4) Twilio CLIの設定

パス:

  • ~/.twilio-cli/config.json

プロファイルの例:

{
  "activeProfile": "prod",
  "profiles": {
    "prod": {
      "accountSid": "YOUR_ACCOUNT_SID",
      "apiKeySid": "YOUR_API_KEY_SID",
      "apiKeySecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    },
    "staging": {
      "accountSid": "YOUR_ACCOUNT_SID",
      "apiKeySid": "YOUR_API_KEY_SID",
      "apiKeySecret": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
    }
  }
}

統合パターン

Twilio Verifyとの連携(ルームへの参加前にステップアップ認証を実施)

パターン:

  1. ユーザーがプライマリ認証でログインします。
  2. リスクの高い操作(有料セッションへの参加、レコーディングが有効なルームなど)では、Verifyチャレンジを必須にします。
  3. Verifyの成功後にのみVideoトークンを発行します。

疑似フロー:

  • POST /verify/start → Twilio Verify V2
  • POST /verify/check
  • POST /video/token(認証済みセッションが必要)

運用上の注意:

  • ユーザー/IPごとにトークン発行をレート制限します。
  • Verifyの判定をTTL付きで保存します(例:10分)。
Programmable Messagingとの連携(セッション通知)

Messagingを使用して、次の内容を送信します:

  • 「ルーム開始」のリマインダー
  • Composition完了後の「レコーディングを利用できます」というリンク

本番環境での要件:

  • STOP/オプトアウトを処理します。
  • 配信失敗のステータスコールバックを処理します。
  • 長い番号を使用する場合は、10DLC登録(米国A2P)が必要です。

パイプラインの例:

  1. Compositionコールバックのcompleted
  2. メディアをS3に保存します。
  3. リンクをSMSで送信します。
  4. ステータスWebhookで配信状況を追跡します。
SendGridとの連携(レコーディングの配信と監査)

SendGridのトランザクションメール:

  • レコーディングのリンク、セッションのメタデータ、保持ポリシーを含む動的テンプレート
  • バウンスやスパムを処理し、無効な宛先を抑制します
Studio でオーケストレーションを構成する

Studio Flow を使用して、次の処理をオーケストレーションします。

  • セッション前のリマインダー
  • セッション後のアンケート
  • 構成に失敗した場合のサポートへのエスカレーション

ルームの終了時に、バックエンドから REST Trigger API を介して Studio を起動します。

CI/CD パイプライン(キーのローテーション+スモークテスト)
  • API キーを毎月ローテーションします。
    • 新しいキーを作成する
    • 2 つのキー(active + next)に対応した状態でデプロイする
    • 署名キーを切り替える
    • TTL の猶予期間後に古いキーを失効させる

スモークテストスクリプト:

  • ルームを作成する
  • トークンを発行する
  • (任意)JS SDK のヘッドレス接続テスト(Playwright)
  • ルームを完了する
  • コンポジションを作成する
  • コールバックが受信されたことを確認する

エラー処理とトラブルシューティング

これらを運用手順書に含め、ログに Twilio のリクエスト ID が記録されるようにしてください。

1) 認証失敗(認証情報が正しくない)

エラー(REST):

TwilioRestException: Authenticate
HTTP 401
{"code":20003,"message":"Authenticate","more_info":"https://www.twilio.com/docs/errors/20003","status":401}

根本原因:

  • API Key/Secret が正しくない
  • API Key SID と Auth Token をパスワードとして使用している
  • クライアントの初期化時に Account SID が一致していない

解決方法:

  • TWILIO_ACCOUNT_SID、TWILIO_API_KEY、TWILIO_API_SECRET を確認する
  • API キーを使用する場合、ヘルパーライブラリが accountSid で初期化されていることを確認する
  • 漏えいしたキーをローテーションする
2) レート制限

エラー:

TwilioRestException: Too Many Requests
HTTP 429
{"code":20429,"message":"Too Many Requests","more_info":"https://www.twilio.com/docs/errors/20429","status":429}

根本原因:

  • ルームや参加者の状態を短時間に集中してポーリングしている
  • コンポジションの作成を過剰に再試行している

解決方法:

  • 指数バックオフとジッターを追加する
  • ルームの状態をキャッシュし、間隔の短すぎるポーリングループを避ける
  • 操作をまとめ、一覧取得の呼び出しを減らす
  • Twilio API にサーキットブレーカーを実装する
3) ルーム名が無効/uniqueName が重複している

エラー(典型例):

TwilioRestException: The requested resource /Rooms was not found
HTTP 404

または、エンドポイントに応じた検証エラー。よくある別の問題として、処理中の既存の uniqueName を使ってルームを作成しようとすることがあります。

根本原因:

  • 同時進行するセッションで uniqueName を再利用している
  • ルームを完了していない

解決方法:

  • セッションごとに一意の名前を使用する(タイムスタンプ/UUID を含める)
  • セッション終了時にルームを完了する
  • 固定の名前が必要な場合は、uniqueName で既存のルームを取得し、completed の場合にのみ再利用する
4) トークンが拒否される(クロックスキュー/有効期限切れ)

クライアントエラー(JS SDK):

TwilioError: Access Token expired or invalid

根本原因:

  • サーバーのクロックスキュー
  • TTL が短すぎる
  • 誤った署名キーシークレットでトークンを発行している

解決方法:

  • トークンサービスで NTP が有効になっていることを確認する
  • TTL を 3600 に延長し、期限切れ前に更新する
  • キーのローテーション処理を検証する
5) Webhook の署名検証に失敗する

サービスのログ:

invalid signature

根本原因:

  • 検証で誤った URL を使用している(正確な公開 URL と一致する必要があります)
  • TWILIO_AUTH_TOKEN がない、または正しくない
  • プロキシによって URL/ホストが変更され、内部 URL と外部 URL が一致していない

解決方法:

  • 検証には外部から参照できる URL(スキーム/ホスト/パスを含む)を使用する
  • NGINX が Host と X-Forwarded-Proto を設定していることを確認する
  • 計算された URL をログに記録し、Twilio のリクエストと比較する
6) コンポジションが処理中のまま停止する/完了しない

コンポジションのステータスが長時間 processing のままになります。

根本原因:

  • ルームの時間が非常に長い
  • レイアウト設定の問題
  • ソーストラックが存在しない、または予期せず終了している
  • Twilio 側で処理が遅延している

解決方法:

  • コンポジションの作成前にルームが完了していることを確認する(または処理に時間がかかることを許容する)
  • 幅広くキャプチャするには、audioSources: "*" と video_sources: ["*"] を使用する
  • ウォッチドッグを追加する:processing がしきい値(例:2 時間)を超えた場合にアラートを出し、Composition SID を添えて Twilio サポートチケットを作成する
7) 企業のファイアウォールの背後で参加者が接続できない

クライアントのログに ICE の失敗が表示されます。典型的な症状は、「接続中…」の後に切断されることです。

根本原因:

  • UDP がブロックされており、TURN over TCP/TLS が必要
  • ディープパケットインスペクションが WebRTC に干渉している

解決方法:

  • 外向きの TCP 443(Twilio TURN over TLS)が許可されていることを確認する
  • Twilio のドメインを許可リストに追加するよう案内する
  • ネットワークの制約が特に厳しい場合は、音声のみ、または PSTN(Twilio Voice)へのフォールバックを検討する
8) メディアの権限が拒否される(ブラウザー)

ブラウザーエラー:

NotAllowedError: Permission denied

根本原因:

  • ユーザーがマイク/カメラを拒否した
  • セキュアでないコンテキスト(HTTPS ではない)
  • iOS Safari ではキャプチャの開始にユーザーの操作が必要

解決方法:

  • HTTPS を必須にする
  • わかりやすい UI で促し、再試行できるようにする
  • ユーザーの操作後に権限を要求する
9) 「Participant identity already exists」(同じルーム内でのID重複)

一部の SDK では、次のように表示されます。

TwilioError: Participant identity is already in use

根本原因:

  • 古いセッションを切断せずに、同じ ID で再接続している
  • 一意の ID サフィックスを付けずに、複数のタブから参加している

対処方法:

  • ID ごとに単一のセッションのみを許可します(古い参加者を退出させる)
  • ID の形式に userId:deviceId を使用し、アプリケーション層でユーザーに対応付けます
10) 21211 invalid To(クラスター構成での関連性)

SMS 通知を送信していて、次のようなエラーが表示される場合:

{"code":21211,"message":"The 'To' number +1555... is not a valid phone number.","status":400}

対処方法:

  • E.164 形式に正規化します
  • 送信前に電話番号を検証します
  • 認証済みの番号のみを保存します

セキュリティ強化

シークレット管理
  • TWILIO_API_SECRET と TWILIO_AUTH_TOKEN はシークレットマネージャーに保存します:
    • AWS Secrets Manager / GCP Secret Manager / Vault
  • シークレットをコンテナイメージに埋め込まないでください。
  • API キーを定期的にローテーションし、使用していないキーは失効させます。
Webhook の検証
  • 必ず X-Twilio-Signature を検証します。
  • パスを厳密に照合し、想定外のメソッドを拒否します。
  • 冪等性キーを実装します:
    • DB で CompositionSid + Status に一意制約を設定します
最小権限と分離
  • サービスを分離します:
    • トークン発行サービス(API Key Secret)
    • Webhook 検証サービス(Auth Token)
  • 環境ごと、サービスごとに異なる Twilio API キーを使用します。
TLS とヘッダー
  • TLS 1.2 以上を必須とします(TLS 1.3 を推奨)。
  • ドメインに HSTS を設定します。
  • Web アプリでは、カメラ/マイク向けに CSP と Permissions-Policy を設定します。
OS の強化(CIS 準拠)

Linux ホストの場合(CIS Ubuntu 22.04 LTS のガイダンス):

  • パスワードによる SSH 認証を無効にし、鍵を使用します。
  • セキュリティ更新を自動的に適用します。
  • 可能な限り、外向きの通信を制限します(Twilio エンドポイントと独自のストレージのみ)。
  • systemd を強化します(ユニットファイルの NoNewPrivileges、ProtectSystem=strict などを参照)。
不正利用の防止
  • 次の項目ごとにトークンエンドポイントのレートを制限します:
    • ユーザー ID
    • IP
    • ルーム ID
  • 認証済みセッションを必須とし、匿名でトークンを発行しないでください。
  • サーバー側でルームへの参加資格を検証し、クライアントが指定したルーム名を信頼しないでください。

パフォーマンスのチューニング

API 呼び出しの削減(測定可能)

変更前:アクティブなルームごとに、参加者を 2 秒ごとにポーリングします。

  • 500 ルーム → 250 req/s → 20429 が発生します。

変更後:イベント駆動とキャッシュした状態を使用します:

  • 状態の遷移時(ルームの作成/完了時)または管理者がオンデマンドで操作するときのみポーリングします。
  • 予想される削減率:API トラフィックの 90% 超

実装:

  • ルームのメタデータを TTL 付きで Redis にキャッシュします。
  • 参加者の参加/退出にはクライアント側のイベントを使用し、必要に応じて独自の WebSocket 経由でバックエンドに送信します。
帯域幅プロファイルのチューニング

maxSubscriptionBitrate を設定して、下りのビットレートを制限します:

  • デフォルト(上限なし):グリッド表示では 4~6 Mbps を超えることがあります。
  • チューニング後:コラボレーション用途では 2.5 Mbps に制限します。
  • 予想される効果:中程度の接続環境でフリーズが減り、送信コストが下がります。
発表者のトラック優先度
  • 発表者のビデオを high に、その他を standard/low に設定します。
  • 予想される効果:混雑時にも発表者の映像が安定し、優先度の低いタイルから品質が低下します。
Composition のコスト管理
  • 必要な場合にのみ composition を作成します:
    • ユーザーが録画をリクエストした場合
    • コンプライアンス要件がある場合
  • すべてのルームでデフォルトで composition を作成することは避けます。
クライアント側のキャプチャ制約

発表者には 720p、その他には 360p を使用します:

const localVideoTrack = await Video.createLocalVideoTrack({
  width: 1280,
  height: 720,
  frameRate: 24
});

予想される効果:

  • 1080p と比べてクライアントの CPU 負荷が下がります
  • 内蔵 GPU での安定性が向上します

高度なトピック

マルチリージョンに関する考慮事項
  • Twilio Video のメディアリージョン選択は、レイテンシーに影響する場合があります。
  • トークンサービスは特定のリージョンに依存しないようにします。ただし、対応している場合は、ルーム設定を通じてユーザーを最寄りのリージョンにルーティングすることを検討してください。
  • 地域ごとに RTT と参加時間を測定し、ISP ごとにメトリクスを保存します。
ダウンタイムのないキーのローテーション

パターン:

  • ACTIVE_SIGNING_KEY_SID と NEXT_SIGNING_KEY_SID を保持します。
  • アクティブキーでトークンを発行し、ローテーション期間中はどちらのキーで署名されたトークンも受け入れます。
  • ローテーション手順:
    1. 新しいキーを作成します
    2. 両方のキーを含む設定をデプロイします
    3. アクティブキーを切り替えます
    4. 最大 TTL を超えるまで待ちます
    5. 古いキーを失効させます
冪等なコンポジション作成

「ルーム完了」ハンドラーが2回実行される可能性がある場合は、次のようにします。

  • コンポジションジョブのroomSidにDBの一意制約を設定します。
  • コンポジションがすでに存在する場合は、作成をスキップします。
再接続の集中への対処

ネットワークの一時的な障害が発生すると、多数のクライアントが同時に再接続します。

緩和策:

  • トークンエンドポイントで、バーストを許容するレート制限を設定します。
  • 再接続する前にクライアント側でバックオフします。
  • 再接続時に新しいルームを作成せず、同じルーム名を再利用します。
Safari/iOSの制約
  • 自動再生の制限:音声を開始する前に、ユーザー操作が必要です。
  • カメラ切り替えの動作は異なるため、iOS 17以降でテストします。
  • SafariではH.264のほうが信頼性が高いことがよくあります。コーデックのネゴシエーションを検証してください。
制御プレーン用のデータトラック

データトラックは次の用途に使用します。

  • 「挙手」
  • 「ミュートのリクエスト」
  • 「レイアウトのヒント」

ただし、権限を伴うモデレーションをデータトラックに依存させず、サーバー側のポリシーを適用してください。


使用例

1) 予定されたグループルームを作成し、トークンを発行して、参加者数の上限を適用する

手順:

  1. バックエンドが予定時刻にルームを作成します。
  2. ユーザーがトークンをリクエストし、バックエンドが登録状況を確認します。
  3. クライアントが帯域幅プロファイルを指定して接続します。

バックエンド(Node)でルームを作成します。

const room = await client.video.v1.rooms.create({
  uniqueName: "acme-prod:webinar:2026-02-21T18-00Z",
  type: "group",
  maxParticipants: 100,
  recordParticipantsOnConnect: false
});

トークンエンドポイントがルームをスコープとするJWTを返します。

const jwt = mintVideoToken({
  identity: "tenant_acme:user_42",
  room: "acme-prod:webinar:2026-02-21T18-00Z",
  ttlSeconds: 1800
});
2) モデレーターが迷惑行為をする参加者を切断する

管理UIがバックエンドを呼び出します。

curl -X POST https://video.acme.com/admin/rooms/RM.../participants/PA.../disconnect

バックエンド:

await client.video.v1.rooms(roomSid).participants(participantSid)
  .update({ status: "disconnected" });

監査ログ:

  • 実行者のID
  • 参加者のID
  • タイムスタンプ
  • 理由コード
3) セッション後のコンポジション作成とSMS通知(Messagingクラスターのパターン)

フロー:

  1. ルームが完了します。
  2. コンポジションを作成します。
  3. コールバックのcompleted時にメディアURLを保存し、SMSを送信します。

コンポジションのコールバックハンドラー:

  • 署名を検証します
  • Status=completedの場合、ジョブsend_recording_smsをキューに登録します

SMSを送信します(MessagingサービスでSTOP処理とステータスコールバックを確実に処理してください)。

4) ネットワーク品質に応じてUIを音声のみの状態に切り替える

クライアント:

  • networkQualityLevelChangedをサブスクライブします。
  • レベルが10秒を超えて1以下の場合:
    • ローカルのビデオトラックを無効にします
    • バナーを表示します
  • レベルが20秒を超えて3以上の場合:
    • ビデオを再度有効にします

疑似コード:

let lowSince = null;

room.on("networkQualityLevelChanged", (level) => {
  const now = Date.now();
  if (level <= 1) {
    lowSince ??= now;
    if (now - lowSince > 10000) {
      room.localParticipant.videoTracks.forEach(pub => pub.track.disable());
    }
  } else {
    lowSince = null;
  }
});
5) キーローテーションの訓練(月次)
  1. 新しいAPIキーを作成します。
  2. 両方のシークレットを設定したトークンサービスをデプロイします。
  3. 有効な署名キーを切り替えます。
  4. 新しいトークンで接続できることを検証します。
  5. 2時間後(TTL=3600の場合)に、古いキーを取り消します。

コマンド:

twilio api:core:keys:create --friendly-name "video-prod-2026-03" -o json
twilio api:core:keys:list -o table --properties sid,friendlyName,dateCreated
twilio api:core:keys:remove --sid SKoldkey...
6) インシデント:ピーク時の20429レート制限

緩和策:

  • API呼び出し元のバックオフをすぐに増やします。
  • 必須ではないポーリングエンドポイントを無効にします。
  • ダッシュボードにはキャッシュ済みのルーム状態を使用します。
  • 事後検証でホットループ(participants:list)を特定し、イベント取り込みに置き換えます。

クイックリファレンス

タスクコマンド/API主なフラグ/フィールド
進行中のルームを一覧表示twilio api:video:v1:rooms:list--status in-progress --limit 50 -o table
ルームを作成twilio api:video:v1:rooms:create--unique-name ... --type group --max-participants N
ルームを完了twilio api:video:v1:rooms:update--sid RM... --status completed
参加者を一覧表示twilio api:video:v1:rooms:participants:list--room-sid RM... --status connected
参加者を切断twilio api:video:v1:rooms:participants:update--room-sid RM... --sid PA... --status disconnected
コンポジションを作成twilio api:video:v1:compositions:create--room-sid RM... --audio-sources "*" --format mp4 --status-callback URL
コンポジションを取得twilio api:video:v1:compositions:fetch--sid CJ...
APIキーをローテーションtwilio api:core:keys:create/remove--friendly-name ... / --sid SK...
トークンを発行(Node)AccessToken + VideoGrantidentity、ttl、room

グラフの関係

DEPENDS_ON
  • twilio-core-auth:Account SID、API Key/Secret、Auth Tokenの処理、キーローテーション
  • twilio-webhooks:署名検証、再試行/冪等性のパターン
  • webrtc-client:ブラウザー/デバイスの制約、ICE/TURNの動作、メディア権限
  • observability:構造化ログ、リクエストID、メトリクス、アラート
COMPOSES
  • twilio-messaging:セッションイベント、録画の配信、ステータスコールバック、STOP の処理に対応する SMS/WhatsApp 通知
  • twilio-verify:トークンの発行/録画へのアクセス前に行う追加認証
  • sendgrid-email:録画リンクの送信、監査証跡、バウンス処理に対応するトランザクションメール
  • twilio-studio:リマインダー、アンケート、エスカレーションフローのオーケストレーション
  • twilio-voice:WebRTC が失敗した場合の音声用 PSTN フォールバック。サポート用 IVR
SIMILAR_TO
  • zoom-video-sdk(概念的に類似:ルーム/セッション、トークン、録画)
  • agora-rtc(トラック、チャンネルへの参加、帯域幅プロファイル)
  • daily-webrtc(ルーム、録画、Webhook)

コメント