Skip to content
bitzorcas
EN

Reference

Webhook 签名

HMAC-SHA256 Webhook 请求签名 — 签名生成、时间戳验证、订阅者验证指南和密钥轮换。

Last updated

BitzOrcas 用 HMAC-SHA256 签名每次 Webhook 投递,确保载荷完整性和真实性。订阅者验证签名以确认载荷来自 BitzOrcas。

签名生成

BitzOrcas 使用以下方式生成签名:

signature = HMAC-SHA256(secret, payload + timestamp)

请求头

每次 Webhook 投递包含:

示例描述
X-Webhook-Signaturesha256=abc123...HMAC-SHA256 十六进制摘要
X-Webhook-Timestamp2026-06-22T10:00:00ZISO 8601 UTC 时间戳
X-Webhook-Eventticket.created事件类型
X-Webhook-Delivery-Iddel-789唯一投递标识符
X-Webhook-Subscription-Idsub-456订阅标识符

WebhookSignature 工具

public static class WebhookSignature
{
public static string Compute(
string secret, // 订阅的签名密钥
string payload, // JSON 体
DateTimeOffset timestamp)
{
var message = $"{payload}{timestamp:O}";
var key = Encoding.UTF8.GetBytes(secret);
var data = Encoding.UTF8.GetBytes(message);
using var hmac = new HMACSHA256(key);
var hash = hmac.ComputeHash(data);
return $"sha256={Convert.ToHexString(hash).ToLowerInvariant()}";
}
}

订阅者验证指南

订阅者应该:

  1. 提取头:读取 X-Webhook-SignatureX-Webhook-Timestamp 和原始请求体
  2. 验证时间戳:拒绝超过 5 分钟的请求
  3. 计算预期签名:使用相同密钥和算法
  4. 比较签名:恒定时间比较以防止时序攻击
// 订阅者侧验证
var timestamp = DateTimeOffset.Parse(request.Headers["X-Webhook-Timestamp"]);
if (DateTimeOffset.UtcNow - timestamp > TimeSpan.FromMinutes(5))
return StatusCode(400); // 过期请求
var expectedSig = WebhookSignature.Compute(subscriptionSecret, rawBody, timestamp);
var actualSig = request.Headers["X-Webhook-Signature"];
if (!ConstantTimeEquals(expectedSig, actualSig))
return StatusCode(401); // 无效签名

密钥轮换

订阅支持无停机密钥轮换:

POST /api/webhooks/subscriptions/{id}/rotate-secret

这会生成新密钥,同时保持旧密钥在过渡期内有效。更新你的验证代码以使用新密钥。

安全最佳实践

  • 恒定时间比较:使用 CryptographicOperations.FixedTimeEquals 防止时序攻击
  • 时间戳验证:拒绝过期请求(5 分钟窗口)
  • 仅 HTTPS:Webhook 回调 URL 始终使用 HTTPS
  • 密钥存储:将密钥存储在安全保管库中(不在代码或配置文件中)
  • IP 过滤:配置 IWebhookIpAllowlistPolicy 限制源 IP

另见