BitzOrcas 用 HMAC-SHA256 签名每次 Webhook 投递,确保载荷完整性和真实性。订阅者验证签名以确认载荷来自 BitzOrcas。
签名生成
BitzOrcas 使用以下方式生成签名:
signature = HMAC-SHA256(secret, payload + timestamp)请求头
每次 Webhook 投递包含:
| 头 | 示例 | 描述 |
|---|---|---|
X-Webhook-Signature | sha256=abc123... | HMAC-SHA256 十六进制摘要 |
X-Webhook-Timestamp | 2026-06-22T10:00:00Z | ISO 8601 UTC 时间戳 |
X-Webhook-Event | ticket.created | 事件类型 |
X-Webhook-Delivery-Id | del-789 | 唯一投递标识符 |
X-Webhook-Subscription-Id | sub-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()}"; }}订阅者验证指南
订阅者应该:
- 提取头:读取
X-Webhook-Signature、X-Webhook-Timestamp和原始请求体 - 验证时间戳:拒绝超过 5 分钟的请求
- 计算预期签名:使用相同密钥和算法
- 比较签名:恒定时间比较以防止时序攻击
// 订阅者侧验证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
另见
- Webhooks 模块 — 完整 Webhook 系统
- 认证 — API 调用方的 HMAC 认证