twilio-video について
twilio-videoは、Twilio Videoを使った本番WebRTCシステムの構築・運用ガイドです。開発者向けに、ルーム、トークン、メディアトラック、録画、ネットワーク品質、運用方法を扱います。
主な機能
- 01ルームのライフサイクル管理
- 02Video Access Tokenの発行
- 03メディアトラックの公開・購読・管理
- 04Compositionsによる録画
- 05ネットワーク品質と帯域幅の設定
- 06Webhookとコールバックの処理
活用シーン
- 01ビデオ会議ルームの作成と終了
- 02ユーザーへのルームアクセストークン発行
- 03参加者の管理とモデレーション
- 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.7twilio==9.4.1
- Go:
go >= 1.22.1github.com/twilio/[email protected]
クライアントサイド:
- 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_SIDTWILIO_API_KEYTWILIO_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|completedmaxParticipants:コストと品質を管理するための上限を設定します
参加者とトラック
- 参加者:ルーム内の 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:9f3c2b1atenantId: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
一般的なフロー:
- 必要に応じて参加者の録画を有効にするか、Compositionのソースに依存します。
- ルームの完了時(または必要に応じて)にCompositionを作成します。
- ステータスコールバック(
processing→completed/failed)を受け取ります。 - 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との連携(ルームへの参加前にステップアップ認証を実施)
パターン:
- ユーザーがプライマリ認証でログインします。
- リスクの高い操作(有料セッションへの参加、レコーディングが有効なルームなど)では、Verifyチャレンジを必須にします。
- Verifyの成功後にのみVideoトークンを発行します。
疑似フロー:
POST /verify/start→ Twilio Verify V2POST /verify/checkPOST /video/token(認証済みセッションが必要)
運用上の注意:
- ユーザー/IPごとにトークン発行をレート制限します。
- Verifyの判定をTTL付きで保存します(例:10分)。
Programmable Messagingとの連携(セッション通知)
Messagingを使用して、次の内容を送信します:
- 「ルーム開始」のリマインダー
- Composition完了後の「レコーディングを利用できます」というリンク
本番環境での要件:
- STOP/オプトアウトを処理します。
- 配信失敗のステータスコールバックを処理します。
- 長い番号を使用する場合は、10DLC登録(米国A2P)が必要です。
パイプラインの例:
- Compositionコールバックの
completed - メディアをS3に保存します。
- リンクをSMSで送信します。
- ステータス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に一意制約を設定します
- DB で
最小権限と分離
- サービスを分離します:
- トークン発行サービス(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を保持します。- アクティブキーでトークンを発行し、ローテーション期間中はどちらのキーで署名されたトークンも受け入れます。
- ローテーション手順:
- 新しいキーを作成します
- 両方のキーを含む設定をデプロイします
- アクティブキーを切り替えます
- 最大 TTL を超えるまで待ちます
- 古いキーを失効させます
冪等なコンポジション作成
「ルーム完了」ハンドラーが2回実行される可能性がある場合は、次のようにします。
- コンポジションジョブの
roomSidにDBの一意制約を設定します。 - コンポジションがすでに存在する場合は、作成をスキップします。
再接続の集中への対処
ネットワークの一時的な障害が発生すると、多数のクライアントが同時に再接続します。
緩和策:
- トークンエンドポイントで、バーストを許容するレート制限を設定します。
- 再接続する前にクライアント側でバックオフします。
- 再接続時に新しいルームを作成せず、同じルーム名を再利用します。
Safari/iOSの制約
- 自動再生の制限:音声を開始する前に、ユーザー操作が必要です。
- カメラ切り替えの動作は異なるため、iOS 17以降でテストします。
- SafariではH.264のほうが信頼性が高いことがよくあります。コーデックのネゴシエーションを検証してください。
制御プレーン用のデータトラック
データトラックは次の用途に使用します。
- 「挙手」
- 「ミュートのリクエスト」
- 「レイアウトのヒント」
ただし、権限を伴うモデレーションをデータトラックに依存させず、サーバー側のポリシーを適用してください。
使用例
1) 予定されたグループルームを作成し、トークンを発行して、参加者数の上限を適用する
手順:
- バックエンドが予定時刻にルームを作成します。
- ユーザーがトークンをリクエストし、バックエンドが登録状況を確認します。
- クライアントが帯域幅プロファイルを指定して接続します。
バックエンド(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クラスターのパターン)
フロー:
- ルームが完了します。
- コンポジションを作成します。
- コールバックの
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) キーローテーションの訓練(月次)
- 新しいAPIキーを作成します。
- 両方のシークレットを設定したトークンサービスをデプロイします。
- 有効な署名キーを切り替えます。
- 新しいトークンで接続できることを検証します。
- 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 + VideoGrant | identity、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)