ゲーム内のすべてのイベントやユーザーからのリクエストを手動で監視するのではなく、ウェブフックを設定してサードパーティのメッセージングツールまたはHTTPリクエストを受け取れるカスタムエンドポイントでリアルタイム通知を受け取ることができます。これにより、通知管理ワークフローを自動化し、通知の取り扱いにかかる手作業を減らすことができます。
ウェブフックワークフロー
ウェブフックは、Robloxとサードパーティのメッセージングツールなど、2つの異なるアプリケーションまたはサービス間でリアルタイムの通知またはデータを送信します。従来のAPIとは異なり、クライアントアプリケーションを設定してサーバーにリクエストを送信しデータを受け取る必要がなく、ウェブフックはイベントが発生するとすぐにクライアントエンドポイントにデータを送信します。これは、チームと協力するために使用するサードパーティアプリケーションとの間でワークフローを自動化するのに役立ち、リアルタイムでのデータ共有と処理を可能にします。
ウェブフックを設定すると、ターゲットイベントが発生するたびに、Robloxは指定したウェブフックURLにリクエストを送信します。ウェブフックURLはリクエストを受信アプリケーションまたはカスタムエンドポイントにリダイレクトし、そこではウェブフックペイロードに含まれるデータに基づいてアクションを取ることができます。これには、GDPRに基づくデータの消去、ユーザーへの確認の送信、または別のイベントのトリガーが含まれる可能性があります。
サポートされているトリガー
Robloxは現在、以下のイベントトリガーをサポートしています。
サブスクリプション
- サブスクリプション再購読 - ユーザーがサブスクリプションを再購読する際に、サブスクリプションと購読者を含むメッセージが送信されます。
- サブスクリプション更新 - ユーザーがサブスクリプションを更新すると、サブスクリプションと購読者を含むメッセージが送信されます。
- サブスクリプション返金 - ユーザーがサブスクリプションの返金を受けると、サブスクリプションと購読者を含むメッセージが送信されます。
- サブスクリプション購入 - ユーザーがサブスクリプションを購入すると、サブスクリプションと購読者を含むメッセージが送信されます。
- サブスクリプションキャンセル - ユーザーがサブスクリプションをキャンセルすると、サブスクリプションと購読者、およびキャンセルの理由が記載されたメッセージが送信されます。
サブスクリプションイベントおよびそのフィールドに関する詳細は、サブスクリプションリファレンスを参照してください。
コンプライアンス
- 消去要求の権利 - ユーザーが一般データ保護規則(GDPR)に基づくデータ削除要求を提出したとき。
コマース
- コマース製品注文の返金 - ユーザーがコマース製品の注文の返金を受けた場合、または注文がキャンセルされた場合。
- コマース製品注文の支払い - ユーザーがコマース製品の注文を支払った場合。重複するウェブフックイベントが発生する可能性があるため、一意のコマース注文IDを使用してイベントを重複排除してください。
Creator Dashboardでウェブフックを設定する
ウェブフックを通じて通知を受け取るには、通知をトリガーする特定のイベントにサブスクライブするウェブフックを構成する必要があります。グループが所有するゲームの場合、グループの所有者のみがウェブフックを構成し、通知を受け取ることができます。
ウェブフックを設定するには:
Creator Hubで自分の体験を選択します。
設定の下で、ウェブフックを選択し、ウェブフックを追加をクリックします。
ウェブフックURLは、提供元から取得します。たとえば、SlackのURLは次のようになります:
https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXXウェブフックURLと名前を入力します。
(オプション)秘密を含めて、受け取る通知がRobloxから来ていることを確認します。詳細は、ウェブフックセキュリティの検証を参照してください。
通知を受け取りたいイベントのサポートされているトリガーリストから1つ以上のオプションを選択します。
(オプション)テストレスポンスボタンを使用して、サービスがサンプルリクエストを受け取れるか確認します。
変更を保存をクリックします。
ウェブフックURLの設定
ウェブフックURLとしてカスタムHTTPサービスエンドポイントを設定できますが、以下の要件を満たす必要があります。
- リクエストを処理するために公開されている必要があります。
- POSTリクエストを処理できる必要があります。
- 5秒以内に2XXレスポンスでリクエストに応答できる必要があります。
- HTTPSリクエストを処理できる必要があります。
エンドポイントがPOSTリクエストを受け取ったときは、次のことができる必要があります:
- POSTメッセージの本文から通知に関する必要な詳細を抽出します。
- 通知に関する一般的な詳細とイベントタイプに関連する特定の詳細を含むPOSTメッセージの本文を読み取ります。
処理するPOSTリクエストのスキーマの詳細については、ペイロードスキーマを参照してください。
配信失敗の再試行ポリシー
ウェブフック通知が、エンドポイントが利用できないなどのエラーにより指定したURLに到達できない場合、Robloxは構成されたURLにメッセージを5回再送信します。通知が5回試行した後も配信できない場合、Robloxは通知の送信を停止し、URLがもはや有効でないと見なします。この場合、通知を受け取れる新しいURLでウェブフック構成を更新する必要があります。ウェブフックURLが正常に通知を受け取れるかをトラブルシューティングし確認するには、ウェブフックのテストを参照してください。
サードパーティ要件
サードパーティツールには、ウェブフックの設定時に従う必要がある独自の要件があります。ターゲットツールのサポートまたはドキュメントサイトで「ウェブフック」というキーワードで検索することで、これらの要件を見つけることができます。サポートされているサードパーティツールは以下の通りです:
ウェブフックのテスト
Creator Dashboardで、構成したウェブフックが通知を正常に受信できるかをテストできます:
- ウェブフック設定ページに移動します。
- 設定されているウェブフックのリストからテストしたいウェブフックを選択します。
- 対象のウェブフックの隣にある鉛筆アイコンをクリックします。
- テストレスポンスボタンをクリックします。
システムはSampleNotificationイベントを送信し、このイベントには通知をトリガーしたユーザーのユーザーIDが含まれます。以下に示す通りです:
SampleNotificationスキーマ
{
"NotificationId": "string",
"EventType": "SampleNotification",
"EventTime": "2023-12-30T16:24:24.2118874Z",
"EventPayload": {
"UserId": 1
}
}
ウェブフックをサードパーティサービスと統合している場合は、サードパーティのURLを使用してテストし、サービスがウェブフックから通知を正常に受信できるかを確認できます。ウェブフックを構成する際に秘密を提供した場合は、roblox-signatureを生成し、roblox-signatureロジックをテストする際に使用できます。
ウェブフックセキュリティの検証
ペイロードを受信するようにサーバーを構成すると、エンドポイントに送信されたペイロードをリッスンし始めます。ウェブフックを構成する際に秘密を設定した場合、Robloxは各ウェブフック通知にroblox-signatureを送信し、リクエストが実際にRobloxから来たことを確認します。署名は、カスタムエンドポイントのペイロードヘッダーにあり、サードパーティのサーバーのフッターにあります。
カスタムエンドポイント用の秘密を含む署名形式t=<timestamp>,v1=<signature>
ウェブフックに秘密を設定しなかった場合、署名には通知が送信された時刻のタイムスタンプのみが含まれます:
カスタムエンドポイント用の秘密なしの署名形式
t=<timestamp>
署名を検証するには:
タイムスタンプと署名の値を抽出します。秘密を持つウェブフックのすべての署名は、これら2つの値に続いてプレフィックスを持つCSV文字列の形式を共有します:
- t: 通知が送信された時刻のタイムスタンプ。
- v1: Creator Dashboard構成で提供された秘密を使用して生成された署名値。
roblox-signatureのベース文字列を再作成します。これは次の要素を連結します:
- タイムスタンプを文字列として。
- ピリオド文字.。
- リクエスト本文のJSON文字列。
構成中に定義した秘密をキーとして使用し、手順2で生成したベース文字列をメッセージとして使用して、SHA256ハッシュ関数を使用してHMACを計算します。結果をBase64形式に変換して、期待される署名を取得します。
抽出した署名値を期待される署名と比較します。正しく署名を生成した場合、その値は同じであるべきです。
(オプション)再送信攻撃を防ぐための手段として、攻撃者がデータを傍受して再送信し、不正にアクセスしたり悪意のある行動を行ったりするタイプのサイバー攻撃であるため、抽出したタイムスタンプ値を現在のタイムスタンプと比較し、合理的な時間制限内に収まっていることを確認することが役立ちます。例えば、10分間のウィンドウは通常の合理的な時間制限です。
ペイロードスキーマ
ウェブフックのターゲットイベントがトリガーされると、通知に関する情報をペイロードに含めてウェブフックURLにリクエストを送信します。すべてのリクエストのペイロードは、固定フィールドと可変フィールドで構成される同じスキーマを共有します。これにより、ペイロードで送信されるデータが構造化され、一貫性が保たれるため、受信アプリケーションがデータを処理し利用しやすくなります。
固定ペイロードスキーマフィールドは、すべてのウェブフックリクエストで一貫性を保つのに役立ち、次のフィールドが利用可能です:
- NotificationId (string): 送信される各通知の一意の識別子。同じNotificationIdが2回受信された場合、それは重複と見なされます。
- EventType (string): 通知がトリガーされたイベントの種類を示します。
- EventTime (string): イベントがトリガーされた時刻のタイムスタンプ。
可変ペイロードスキーマフィールドは、さまざまなイベントタイプをサポートするための柔軟性を提供し、以下の内容が含まれます:
- EventPayload (オブジェクト): ウェブフックをトリガーしたEventTypeに特有の情報を含みます。EventPayloadスキーマの構造は、イベントのタイプによって異なります。
次の例は、消去権要求イベントのペイロードスキーマを示しています:
消去権要求の例
{
"NotificationId": "string",
"EventType": "RightToErasureRequest",
"EventTime": "2023-12-30T16:24:24.2118874Z",
"EventPayload": {
"UserId": 1,
"GameIds": [
1234, 2345
]
}
}
通知の処理
ユーザーの**個人を特定できる情報(PII)**を保持している場合、たとえばユーザーIDなど、ユーザーがそのような要求を提出したときにこの情報を削除する必要があります。これはGDPRの消去権遵守要件に従うためです。ウェブフック通知を処理し、データ削除を自動化するボットを作成できます。ただし、PIIをデータストアに保存している必要があります。Discord内でボットを作成し、データストア用のオープンクラウドAPIを使用してPIIデータを削除する自動化ソリューションの例については、消去権要求削除の自動化を参照してください。この例は、サブスクリプションイベントなどの他の通知を処理するように適応できます。
サードパーティツールではなくカスタムエンドポイントをウェブフックサーバーとして使用する場合、ウェブフックペイロードから削除対象のデータを抽出し、独自の自動化ソリューションを構築できます。以下のコードサンプルは、リプレイ攻撃を防ぐためにタイムスタンプを確認し、リクエストがRobloxから来ていることを確認するサーバーの例です:
ペイロードからのPIIの抽出
const crypto = require('crypto');
const express = require('express');
const secret = '<your_secret>' // これは環境変数として設定できます
let app = express();
app.use(express.json());
app.all('/*', function (req, res) {
console.log('新しいリクエストを受信しました');
// ヘッダーからタイムスタンプと署名を抽出
const signatureHeader = req.headers['roblox-signature'].split(',');
const timestamp = signatureHeader.find(e => e.startsWith('t=')).substring(2);
const signature = signatureHeader.find(e => e.startsWith('v1=')).substring(3);
// リプレイ攻撃を防ぐために、リクエストが300秒のウィンドウ内にあったことを確認
const requestTimestampMs = timestamp * 1000;
const windowTimeMs = 300 * 1000;
const oldestTimestampAllowed = Date.now() - windowTimeMs;
if (requestTimestampMs < oldestTimestampAllowed) {
return res.status(403).send('期限切れのリクエスト');
}
// 署名を検証
const message = `${timestamp}.${JSON.stringify(req.body)}`;
const hmac = crypto.createHmac('sha256', secret);
const calculatedSignature = hmac.update(message).digest('base64');
if (signature !== calculatedSignature) {
return res.status(401).send('未承認のリクエスト');
}
// ペイロードを処理するためのあなたのロジック
const payloadBody = req.body;
const eventType = payloadBody['EventType'];
if (eventType === 'RightToErasureRequest'){
const userId = payloadBody['EventPayload']['UserId'];
const gameIds = payloadBody['EventPayload']['GameIds'];
console.log(`ペイロードデータ: UserId=${userId} と GameIds=${gameIds}`);
// データストアにPIIを保存している場合は、UserIdとGameIdsを使用してデータストアから情報を削除します。
}
return res.json({ message: 'メッセージが正常に処理されました' });
});
app.listen(8080, function () {
console.log('サーバーが起動しました');
});