Webhook 通知

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

您可以设置 webhooks 来接收第三方消息工具或自定义端点上的实时通知,而不是手动监控体验中的所有事件和用户请求,这可以减少您接收 HTTP 请求的工作量。这有助于自动化您的通知管理工作流程,减少手动处理通知的劳力。

Webhook 工作流

Webhook 可以在两个不同应用程序或服务之间发送实时通知或数据,例如 Roblox 和第三方消息工具。与传统 API 不同,它们需要您设置客户端应用程序向服务器发送请求以接收数据,而网络钩在发生事件后立即将数据发送到您的客户端端点。它们有助于自动化 Roblox 和第三方应用程序之间的工作流程,因为它们允许实时数据共享和处理。

一旦你设置了一个网络钩,每当目标事件发生时,Roblox都会向你提供的网络钩 URL发送请求。然后,Webhook URL 将请求重定向到您的接收应用程序或自定义终点,该终点可以根据 Webhook payload 中包含的数据采取行动。这可能包括为GDPR清除数据、向用户发送确认或触发另一个事件。

支持的触发器

Roblox 目前支持以下事件触发通知:

  • 订阅取消了 - 当用户取消订阅时,会发送包含订阅和订阅人以及取消原因的消息。
  • 订阅已购买 - 当用户购买订阅时,会发送包含订阅和订阅者的消息。
  • 订阅退款 - 当用户收到订阅退款时,会发送包含订阅和订阅者的消息。
  • 订阅已续订 - 当用户续订订阅时,会发送包含订阅和订阅者的消息。
  • 订阅重新订阅 - 当用户重新订阅订阅时,会发送包含订阅和订阅者的消息。
  • “被遗忘的权利” 数据删除请求在一般数据保护条例下( GDPR )。

了解有关订阅事件和其领域的更多信息,请参阅云API订阅参考。

在创建者仪表板上配置网络钩

要通过网络钩接收通知,您需要配置一个网络钩,订阅特定事件以触发通知。对于群组拥有体验,只有群组所有者可以配置并接收 Webhook 通知。

要设置一个网络钩:

  1. 导航到创建者仪表板的 网络钩 部分。
  2. 点击 添加Webhook 按钮。
  3. 完成配置字段:
    1. 网络请求URL — 指定您想要接收通知和接受第三方实体发出的 Webhook URL 的URL。了解有关需求的更多信息,请参阅设置 Webhook URL
    2. 名称 — 使用自定义名称来区分您的配置与其他配置。默认值与 Webhook URL 相同。
    3. 秘密 (可选) — 提供秘密,如果你想验证收到的通知来自 Roblox。了解更多信息,请参阅验证Webhook安全
    4. 触发器 — 从列表中选择一个或多个选项,用于接收通知的事件,其中包括你想要收到通知的支持触发器
  4. 点击 保存更改 按钮。

设置 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” 来找到这些要求。对于三个支持的第三方工具,请参阅以下内容:

测试网络端点

您可以测试您配置的网络钩是否能在 创建者仪表板 上成功接收通知:

  1. 导航到 Webhooks 配置页面。
  2. 从配置的网络请求列表中选择要测试的网络请求。
  3. 单击目标网络钩旁边的铅笔图标。
  4. 点击 测试响应 按钮。

系统然后发送通知在 SampleNotification 输入,其中包含触发通知的用户的 用户ID ,如下示例图所示:

样本通知模型

Body: {
"NotificationId": "string",
"EventType": "SampleNotification",
"EventTime": "2023-12-30T16:24:24.2118874Z", // 类型:ISO 8601时间戳
"EventPayload": {
"UserId": 1 // 类型:长
}
}

如果您将网络钩与第三方服务集成,您可以使用第三方 URL 进行测试,以确认服务可以成功接收您的网络钩通知。如果您在配置 Webhook 时提供秘密,它还会生成一个 roblox-signature 可用于测试 roblox-signature 逻辑的地方。

验证 webhook 安全

一旦您配置服务器接收 payload,它就开始收听端点发送的任何 payload。如果您在配置 Webhook 时设置秘密,Roblox 将向每个 Webhook 通知发送 roblox-signature 以帮助保护您的数据安全。这样,您可以使用它来验证通知来自 Roblox,并将服务器限制仅接收来自 Roblox 的请求。签名位于自定义端点的付载头和第三方服务器的脚注中。

Signature Format with Secret for Custom Endpoints

"roblox-signature": "t=<timestamp>,v1=<signature>"

如果你没有为你的 webhook 设置秘密,那么你收到的签名只包含通知发送时的时戳值:

Signature Format without Secret for Custom Endpoints

"roblox-signature": "t=<timestamp>"

要验证签名:

  1. 提取时间戳和签名值。所有带有秘密的 webhook 的签名都与这两个值后面的 CSV 字符串的格式相同:

    • t : 通知发送时的时戳值。
    • v1 : 使用创建者面板配置提供的秘密生成的签名值。您可以使用 split() 函数来提取这两个值,该函数根据分隔符将字符串分开,在这种情况下,是 , 字符。
  2. 通过 concatenate 重创 roblox-signature 的基础字符串:

    1. 时间戳作为字符串。
    2. 时间字符 .
    3. 请求身体的 JSON 字符串。
  3. 使用配置期间定义的秘密和步骤 2 生成的基础字符串计算出基于哈希的消息验证代码(HMAC),使用 SHA256 哈希函数验证消信息。将结果转换为 Base64 格式以获得期望的签名。

  4. 将提取的签名值与期望的签名进行比较。如果你正确生成了签名,值应相同。

  5. (可选) 为了防止重播攻击,攻击者将数据截获并重发以获得未经授权的访问或执行恶意行动的一种类型的网络攻击,与当前时间戳比较获取的时间戳值是有用的,以确保它落在合理的时间限制内。例如,10分钟的窗口通常是一个很好的合理时间限制。

付载方案

当您的 webhook 的目标事件触发时,它会向您的 webhook URL 发送请求,包括 payload 中事件信息。所有请求的付载都共享相同的 schema,其中包含固定和可变的字段。这可以确保在 payload 中传输的数据被结构化并一致,使接收应用程序更容易处理和使用数据。 固定的付载协议字段 可以帮助保持所有 Webhook 请求的一致性,可用以下字段:

  1. NotificationId , string : 每次发送的通知的唯一标识符。如果收到相同的 NotificationId 两次,它被视为重复。

  2. EventType , string : 一条字符串代表触发通知的事件类型。

  3. EventTime , timestamp : 表示事件触发时间的大约时戳。 变量载荷方案字段 为网络提供了灵活性,可以容纳各种类型的事件,这些事件包括:

  4. EventPayload , object : 包含触发 Webhook 的 EventType 特定信息。EventPayload 模型的结构根据事件类型而异。

以下示例显示了 删除请求 事件的payload schema:

删除请求的右侧示例数据模型

Body:{
"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');
let app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// This is a sample only code
app.all('/*', function (req, res) {
console.log('-------- New Request Seen -------');
// 1. Extract the timestamp and signature
const shared_secret = '<Your secret>' // This can be set as an environment variable
const hmac = crypto.createHmac('sha256', shared_secret)
const roblox_signature_header = req.headers['roblox-signature'].split(',')
// 'roblox-signature' is present in all requests:
// Timestamp(t) is present in all requests, however signature value(v1) is not set unless a secret is shared during the webhook configuration.
// Fetch header component at Index 0 -> 't=' and Index 1 -> 'v1='
const timestamp = roblox_signature_header.find(e => e.startsWith('t=')).substring(2);
const extracted_signature = roblox_signature_header.find(e => e.startsWith('v1='));
// 2. Prevent Replay attack: 300 seconds window
const request_timestamp_ms = timestamp * 1000;
const window_time_ms = 300 * 1000
const oldest_timestamp_allowed = Date.now() - window_time_ms;
if (request_timestamp_ms < oldest_timestamp_allowed) {
res.status(403).send('Expired Request')
}
// 3. Validate Signature
if (extracted_signature !== undefined) {
const signature_v1 = extracted_signature.substring(3);
const message = `${timestamp}.${JSON.stringify(req.body)}`
const base64_signature = hmac.update(message).digest('base64')
if (signature_v1 !== base64_signature) {
res.status(401).send('Unauthorized Request')
}
}
// 4. 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']
const gameIdString = gameIds.toString()
console.log(`The payload: UserId=${userId} and GameIds=${gameIdString}`)
// If you store PII in data stores, use the UserId and GameIds to make a data store call to delete the information.
}
// 5. Return Response
res.json({ message: 'Processed the message Successfully' });
})
app.listen(8080, function () {
console.log('This is a Sample application')
})