How to fix Insecure Webhooks
in ServiceStack
Executive Summary
Insecure webhooks are a goldmine for attackers. If your ServiceStack endpoint processes incoming webhooks without verifying the payload signature, you've effectively left a backdoor open. Attackers can forge events, bypass payment gateways, or trigger administrative actions by simply POSTing a crafted JSON body to your endpoint. To secure this, you must implement HMAC-based signature verification using a shared secret.
The Vulnerable Pattern
[Route("/webhooks/process", "POST")] public class ProcessWebhook : IReturnVoid { public string EventType { get; set; } public Guid OrderId { get; set; } }
public class WebhookService : Service { public void Post(ProcessWebhook request) { // VULNERABILITY: No source verification. // Anyone can send a POST request to this endpoint. if (request.EventType == “payment.succeeded”) { OrderManager.MarkAsPaid(request.OrderId); } } }
The Secure Implementation
The vulnerability lies in trusting the HTTP request source implicitly. The fix implements a 'Shared Secret' architecture. 1) Raw Body Access: We use Request.GetRawBodyAsync() because signature verification must be performed on the original payload, not a deserialized object which might have different formatting. 2) HMAC-SHA256: We compute a hash of the raw body using a secret key known only to the provider and our service. 3) Constant-Time Comparison: We use FixedTimeEquals to compare the computed signature against the header signature to mitigate side-channel timing attacks. If the signatures don't match, the request is discarded before any business logic executes.
[Route("/webhooks/process", "POST")] public class ProcessWebhook : IReturnVoid { }public class WebhookService : Service { private const string WebhookSecret = “whsec_f829…032”;
public async Task Post(ProcessWebhook request) { // SECURE: Get raw body for cryptographic verification string json = await Request.GetRawBodyAsync(); string headerSignature = Request.Headers["X-Webhook-Signature"]; if (string.IsNullOrEmpty(headerSignature)) throw HttpError.Unauthorized("Missing signature."); // Compute HMACSHA256 hash using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(WebhookSecret))) { var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(json)); var computedSignature = BitConverter.ToString(hash).Replace("-", "").ToLower(); // Constant-time comparison to prevent timing attacks if (!CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(computedSignature), Encoding.UTF8.GetBytes(headerSignature))) { throw HttpError.Unauthorized("Invalid signature."); } } // Proceed only after verification var data = json.FromJson<ProcessWebhook>(); if (data.EventType == "payment.succeeded") { OrderManager.MarkAsPaid(data.OrderId); } }
}
Your ServiceStack API
might be exposed to Insecure Webhooks
74% of ServiceStack apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.
Free Tier • No Credit Card • Instant Report
Verified by Ghost Labs Security Team
This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.