BitzOrcas signs every webhook delivery with HMAC-SHA256 to ensure payload integrity and authenticity. Subscribers verify signatures to confirm payloads originated from BitzOrcas.
Signature generation
BitzOrcas generates signatures using:
signature = HMAC-SHA256(secret, payload + timestamp)Request headers
Every webhook delivery includes:
| Header | Example | Description |
|---|---|---|
X-Webhook-Signature | sha256=abc123... | HMAC-SHA256 hex digest |
X-Webhook-Timestamp | 2026-06-22T10:00:00Z | ISO 8601 UTC timestamp |
X-Webhook-Event | ticket.created | Event type |
X-Webhook-Delivery-Id | del-789 | Unique delivery identifier |
X-Webhook-Subscription-Id | sub-456 | Subscription identifier |
WebhookSignature utility
public static class WebhookSignature{ public static string Compute( string secret, // Subscription's signing secret string payload, // JSON body 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()}"; }}Subscriber verification guide
Subscribers should:
- Extract headers: Read
X-Webhook-Signature,X-Webhook-Timestamp, and the raw request body - Validate timestamp: Reject requests older than 5 minutes
- Compute expected signature: Using the same secret and algorithm
- Compare signatures: Constant-time comparison to prevent timing attacks
// Subscriber-side verificationvar timestamp = DateTimeOffset.Parse(request.Headers["X-Webhook-Timestamp"]);if (DateTimeOffset.UtcNow - timestamp > TimeSpan.FromMinutes(5)) return StatusCode(400); // Stale request
var expectedSig = WebhookSignature.Compute(subscriptionSecret, rawBody, timestamp);var actualSig = request.Headers["X-Webhook-Signature"];
if (!ConstantTimeEquals(expectedSig, actualSig)) return StatusCode(401); // Invalid signatureSecret rotation
Subscriptions support secret rotation without downtime:
POST /api/webhooks/subscriptions/{id}/rotate-secretThis generates a new secret while keeping the old one valid for a transition period. Update your verification code to use the new secret.
Security best practices
- Constant-time comparison: Use
CryptographicOperations.FixedTimeEqualsto prevent timing attacks - Timestamp validation: Reject stale requests (5-minute window)
- HTTPS only: Always use HTTPS for webhook callback URLs
- Secret storage: Store secrets in a secure vault (not in code or config files)
- IP filtering: Configure
IWebhookIpAllowlistPolicyto restrict source IPs
See also
- Webhooks module — Full webhook system
- Authentication — HMAC authentication for API callers