您可以设置 webhooks 来接收第三方消息工具或自定义端点上的实时通知,而不是手动监控体验中的所有事件和用户请求,这可以减少您接收 HTTP 请求的工作量。这有助于自动化您的通知管理工作流程,减少手动处理通知的劳力。
Webhook 工作流
Webhook 可以在两个不同应用程序或服务之间发送实时通知或数据,例如 Roblox 和第三方消息工具。与传统 API 不同,它们需要您设置客户端应用程序向服务器发送请求以接收数据,而网络钩在发生事件后立即将数据发送到您的客户端端点。它们有助于自动化 Roblox 和第三方应用程序之间的工作流程,因为它们允许实时数据共享和处理。
一旦你设置了一个网络钩,每当目标事件发生时,Roblox都会向你提供的网络钩 URL发送请求。然后,Webhook URL 将请求重定向到您的接收应用程序或自定义终点,该终点可以根据 Webhook payload 中包含的数据采取行动。这可能包括为GDPR清除数据、向用户发送确认或触发另一个事件。
支持的触发器
Roblox 目前支持以下事件触发器。
订阅
- 订阅重新订阅 - 当用户重新订阅订阅时,会发送包含订阅和订阅者的消息。
- 订阅已续订 - 当用户续订订阅时,会发送包含订阅和订阅者的消息。
- 订阅退款 - 当用户收到订阅退款时,会发送包含订阅和订阅者的消息。
- 订阅已购买 - 当用户购买订阅时,会发送包含订阅和订阅者的消息。
- 订阅取消了 - 当用户取消订阅时,会发送包含订阅和订阅人以及取消原因的消息。
了解有关订阅事件和其领域的更多信息,请参阅订阅参考。
遵守
- 删除请求权 - 当用户在 一般数据保护条例 (GDPR) 下提交数据删除请求时。
商业
- 商业产品订单退还 - 当用户收到商业产品订单的退款,或订单被取消时。
- 商业产品订单已付费 - 当用户已支付他们的商业产品订单时。请注意,重复的 Webhook 事件是可能的,因此您应该使用独特的商业订单 ID 来删除事件。
在创建者仪表板上配置网络钩
要通过网络钩接收通知,您需要配置一个网络钩,订阅特定事件以触发通知。对于群组拥有体验,只有群组所有者可以配置并接收 Webhook 通知。
要设置一个网络钩:
- 导航到创建者仪表板的 网络钩 部分。
- 点击 添加Webhook 按钮。
- 完成配置字段:
- 网络请求 URL - 指定您想要接收通知的 URL。了解有关需求的更多信息,请参阅设置 Webhook URL。
- 名称 - 使用自定义名称来区分您的配置与其他配置。默认值与 Webhook URL 相同。
- 秘密 (可选) - 提供秘密,如果你想验证收到的通知来自 Roblox。了解更多信息,请参阅验证Webhook安全。
- 触发器 - 从列表中选择一个或多个选项,用于接收通知的事件,其中包括你想要收到通知的支持触发器。
- 点击 保存更改 按钮。
设置 webhook URL
您可以将自定义 HTTP 服务端点设置为您的 Webhook URL,只要它符合以下要求:
- 必须公开可用于处理请求。
- 它可以处理POST请求。
- 它可以在 5 秒内回应请求以 2XX 响应。
- 它可以处理 HTTPS 请求。
当您的端点收到 POST 请求时,它必须能够:
- 从 POST 消息的主体中提取关于通知的所需细节。
- 阅读 POST 消息的主体,包括通知的通用细节和与通知相关的事件类型的具体细节。
了解有关处理 POST 请求的 Schema 的更多信息,请参阅 Payload Schema。
交付失败重试政策
当一个 webhook 通知因错误导致无法达到指定的 URL 时,Roblox 会使用固定窗口大小重试将消息发送到配置的 URL 5 次。如果通知仍然在 5 次尝试后仍未能交付,Roblox 将停止尝试发送通知,并假定 URL 已失效。在这种情况下,您需要使用新的 URL 更新您的 Webhook 配置,以便可以接收通知并且可以访问。要排除故障并确认您的 webhook URL 可以成功接收通知,请参阅 测试 webhook。
第三方需求
第三方工具通常有自己的网络钩需求,当设置您的网络钩URL时,您需要遵循这些需求。您可以在目标工具的支持或文档网站上搜索关键字 “webhook” 来找到这些要求。对于三个支持的第三方工具,请参阅以下内容:
测试网络端点
您可以测试您配置的网络钩是否能在 创建者仪表板 上成功接收通知:
- 导航到 Webhooks 配置页面。
- 从配置的网络请求列表中选择要测试的网络请求。
- 单击目标网络钩旁边的铅笔图标。
- 点击 测试响应 按钮。
系统然后发送一个 SampleNotification 事件,其包含用户触发通知的用户的 用户ID ,如图所示:
样本通知架构图
{
"NotificationId": "string",
"EventType": "SampleNotification",
"EventTime": "2023-12-30T16:24:24.2118874Z",
"EventPayload": {
"UserId": 1
}
}
如果您将网络钩与第三方服务集成,您可以使用第三方 URL 进行测试,以确认服务可以成功接收您的网络钩通知。如果您在配置 Webhook 时提供秘密,它还会生成一个 roblox-signature 可以用来测试 roblox-signature 逻辑的地方。
验证 webhook 安全
配置完成后,您的服务器开始收听端点发送的任何付载。如果您在配置 Webhook 时设置秘密,Roblox 在每次 Webhook 通知中发送一个 roblox-signature 以确保请求实际上来自 Roblox。签名位于自定义端点的付载头和第三方服务器的脚注中。
Signature format with a secret for custom endpoints
t=<timestamp>,v1=<signature>
如果您没有为您的 webhook 设置秘密,签名仅包含通知发送时间戳:
Signature format without a secret for custom endpoints
t=<timestamp>
要验证签名:
提取时间戳和签名值。所有带有秘密的 webhook 的签名都与这两个值后面的 CSV 字符串的格式相同:
- t : 通知发送时的时戳。
- v1 : 使用创建者面板配置提供的秘密生成的签名值。
通过 concatenate 重创 roblox-signature 的基础字符串:
- 时间戳作为字符串。
- 时间字符 . 。
- 请求体的 JSON 字符串。
使用配置期间定义的秘密和你通过步骤 2 生成的基础字符串计算基于哈希的消息验证代码(HMAC),使用 SHA256 哈希函数验证消息。将结果转换为 Base64 格式以获得期望的签名。
将提取的签名值与期望的签名进行比较。如果你正确生成了签名,值应相同。
(可选) 为了防止重播攻击,攻击者将数据截获并重发以获得未经授权的访问或执行恶意行动的一种类型的网络攻击,与当前时间戳比较获取的时间戳值是有用的,以确保它落在合理的时间限制内。例如,10分钟的窗口通常是一个很好的合理时间限制。
付载方案
当您的 webhook 的目标事件触发时,它会向您的 webhook URL 发送请求,包括 payload 中事件信息。所有请求的付载都共享相同的 schema,其中包含固定和可变的字段。这可以确保在 payload 中传输的数据被结构化并一致,使接收应用程序更容易处理和使用数据。 固定的付载协议字段 可以帮助保持所有 Webhook 请求的一致性,可用以下字段:
NotificationId (字符串):每次发送的通知的唯一标识符。如果收到相同的 NotificationId 两次,则视为重复。
EventType (字符串):指示触发通知的事件类型。
EventTime (字符串):事件触发时的时戳。 变量载荷方案字段 为网络提供了灵活性,可以容纳各种类型的事件,这些事件包括:
EventPayload (对象):包含触发 Webhook 的 EventType 特定信息。EventPayload 模型的结构根据事件类型会有所变化。
以下示例显示了 删除请求 事件的payload schema:
删除请求的示例数据模型
{
"NotificationId": "string",
"EventType": "RightToErasureRequest",
"EventTime": "2023-12-30T16:24:24.2118874Z",
"EventPayload": {
"UserId": 1,
"GameIds": [
1234, 2345
]
}
}
处理通知
如果您存储任何个人识别信息 (PII)的用户,例如他们的用户ID,当用户提交此类请求来遵守GDPR删除权要求时,您必须删除此信息,以遵守GDPR删除权要求。如果您在数据存储中存储 PII,您可以创建一个机器人来处理网络请求通知并帮助自动删除数据,提高数据删除效率。请参阅自动删除请求权限,了解如何在 Guilded 或 Discord 中创建一个使用 Open Cloud API 为数据存储提供数据存储 来删除 PII 数据的机器人,作为自动化解决方案。这个例子可以用于处理其他通知,例如订阅事件。
如果您使用自定义端点作为您的 Webhook 服务器,而不是第三方工具,您可以从 Webhook payload 中提取需要删除的数据,并构建自己的自动化解决方案。以下代码示例是防止重播攻击的服务器的一个例子,即通过验证时间戳来确保请求来自 Roblox:
Extracting PII from Payload
const crypto = require('crypto');
const express = require('express');
const secret = '<your_secret>' // This can be set as an environment variable
let app = express();
app.use(express.json());
app.all('/*', function (req, res) {
console.log('New request recieved');
// Extract the timestamp and signature from header
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);
// Ensure the request came within a 300 second window to prevent replay attacks
const requestTimestampMs = timestamp * 1000;
const windowTimeMs = 300 * 1000;
const oldestTimestampAllowed = Date.now() - windowTimeMs;
if (requestTimestampMs < oldestTimestampAllowed) {
return res.status(403).send('Expired Request');
}
// Validate signature
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('Unauthorized Request');
}
// Your logic to handle payload
const payloadBody = req.body;
const eventType = payloadBody['EventType'];
if (eventType === 'RightToErasureRequest'){
const userId = payloadBody['EventPayload']['UserId'];
const gameIds = payloadBody['EventPayload']['GameIds'];
console.log(`Payload data: UserId=${userId} and GameIds=${gameIds}`);
// If you store PII in data stores, use the UserId and GameIds to delete the information from data stores.
}
return res.json({ message: 'Processed the message successfully' });
});
app.listen(8080, function () {
console.log('Server started');
});