Round #333

08 May 2026, 16:23· csharp· qwen2.5-coder-7b-instruct· r13-clean-hints
Complexity
band 0
ZP / UO / OBS
5 / 4 / 1
Journey weight
612
Lightshift ΔL
-0.50

Prompt

Implement a webhook delivery endpoint with HMAC signature verification

Zero-point index (what the engine surfaced)

ZPI-ZPI-0003 | 2026-05-08T16:23:38Z Implement a webhook delivery endpoint with HMAC signature verification ZERO POINT — 5 entries (structural invariants) ● hsl(27.69, 100%, 50%) async [feature] ● hsl(145.64, 100%, 27%) Task of T [feature] ● hsl(7.18, 100%, 38%) class [feature] ● hsl(0.00, 100%, 38%) public [feature] ● hsl(84.10, 100%, 38%) namespace [feature] UNOBSERVED — 4 entries (implied truths) ○ hsl(9.23, 100%, 50%) interface [feature] ○ hsl(77.54, 100%, 36%) Routing middleware [function] ○ hsl(83.08, 100%, 42%) ILogger of T injection [function] ○ hsl(31.38, 100%, 42%) Scoped registration [function] OBSERVED — 1 entries (explicit in request) ◉ hsl(98.46, 100%, 28%) with [feature] COMPLEXITY │ 10 entries │ spread: 89° │ band: LOW

Baseline response (no ZPI)

# Webhook Delivery Endpoint with HMAC Signature Verification ## Architecture Overview ```mermaid graph TD A[Incoming HTTP Request] --> B{Validate Headers} B -->|Invalid| Z[Return 401 Unauthorized] B -->|Valid| C[Parse JSON Body] C --> D{Verify HMAC Signature} D -->|Invalid| Z D -->|Valid| E[Process Webhook Payload] E --> F[Log & Persist Event] F --> G[Return 200 OK] ``` ## Implementation Files ### 1. **WebhookService.cs** - Core HMAC Verification Logic ```csharp using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Text; using Microsoft.Extensions.Options; namespace WebhookDelivery.Services { /// <summary> /// Service for verifying webhook signatures and processing payloads. /// </summary> public interface IWebhookService { bool VerifySignature(string payload, string signature, string secret); void ProcessPayload(WebhookPayload payload); WebhookResult ProcessRequest(string rawBody, string signatureHeader, Dictionary<string, string> headers); } /// <summary> /// Result of webhook processing. /// </summary> public class WebhookResult { public bool Success { get; set; } public int StatusCode { get; set; } public string Message { get; set; } public static WebhookResult Ok() => new WebhookResult { Success = true, StatusCode = 200, Message = "OK" }; public static WebhookResult Unauthorized() => new WebhookResult { Success = false, StatusCode = 401, Message = "Invalid or missing signature" }; public static WebhookResult BadRequest(string message) => new WebhookResult { Success = false, StatusCode = 400, Message = message }; } /// <summary> /// Configuration for webhook service. /// </summary> public class WebhookOptions { public string SecretKey { get; set; } public int MaxBodySize { get; set; } = 10 * 1024 * 1024; // 10MB default public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30); } /// <summary> /// Webhook payload model. /// </summary> public class WebhookPayload { [JsonProperty("event")] public string EventType { get; set; } [JsonProperty("data")] public Dictionary<string, object> Data { get; set; } = new(); [JsonProperty("timestamp")] public DateTime Timestamp { get; set; } } /// <summary> /// Implementation of webhook service with HMAC verification. /// </summary> public class WebhookService : IWebhookService { private readonly string _secretKey; private readonly int _maxBodySize; public WebhookService(IOptions<WebhookOptions> options) { var config = options.Value; if (string.IsNullOrWhiteSpace(config.SecretKey)) throw new InvalidOperationException("Secret key must be configured"); _secretKey = config.SecretKey; _maxBodySize = config.MaxBodySize; } /// <summary> /// Verifies HMAC-SHA256 signature. /// </summary> public bool VerifySignature(string payload, string signature, string secret) { if (string.IsNullOrEmpty(payload)) return false; if (string.IsNullOrEmpty(signature)) return false; // Remove whitespace and quotes from signature header value var cleanSignature = signature.Trim().Trim('"'); using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var expectedHash = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(payload))); return string.Equals(expectedHash, cleanSignature, StringComparison.OrdinalIgnoreCase); } /// <summary> /// Processes incoming webhook request. /// </summary> public WebhookResult ProcessRequest(string rawBody, string signatureHeader, Dictionary<string, string> headers) { // Validate body size if (string.IsNullOrEmpty(rawBody)) return WebhookResult.BadRequest("Empty request body"); if (rawBody.Length > _maxBodySize) return WebhookResult.BadRequest($"Request body exceeds maximum size of {_maxBodySize} bytes"); // Extract signature from header string? signature = null; // Common signature header names var sigHeaders = new[] { "X-Signature", "X-Hub-Middleware-Event-Signature", "X-Signature-256" }; foreach (var header in sigHeaders) { if (!string.IsNullOrEmpty(headers[header])) { signature = headers[header]; break; } } // If no standard header found, try the raw header value if (signature == null && !string.IsNullOrEmpty(signatureHeader)) { signature = signatureHeader.Trim(); } if (string.IsNullOrEmpty(signature)) return WebhookResult.Unauthorized(); // Verify signature if (!VerifySignature(rawBody, signature, _secretKey)) return WebhookResult.Unauthorized(); // Parse payload try { var payload = JsonSerializer.Deserialize<WebhookPayload>(rawBody); if (payload == null) return WebhookResult.BadRequest("Invalid JSON payload"); // Log and process the webhook event ProcessPayload(payload); return WebhookResult.Ok(); } catch (JsonException ex) { return WebhookResult.BadRequest($"JSON parsing error: {ex.Message}"); } } /// <summary> /// Business logic for processing webhook payload. /// </summary> protected virtual void ProcessPayload(WebhookPayload payload) { // Log the event Console.WriteLine($"Processing webhook event: {payload.EventType}"); // Persist to database or message queue here // Example: _eventRepository.Save(payload); // Trigger downstream services if needed // Example: _eventProcessor.Process(payload.Data); } } } ``` ### 2. **WebhookController.cs** - ASP.NET Core Controller ```csharp using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; using WebhookDelivery.Services; namespace WebhookDelivery.Controllers { /// <summary> /// REST API endpoint for receiving webhook events. /// </summary> [ApiController] [Route("api/webhooks")] public class WebhookController : ControllerBase { private readonly IWebhookService _webhookService; public WebhookController(IWebhookService webhookService) { _webhookService = webhookService; } /// <summary> /// Receives and validates incoming webhook requests. /// </summary> [HttpPost] [Consumes("application/json")] public IActionResult ReceiveWebhook() { // Read raw body before any processing var rawBody = Request.Body.ToString(); // Extract headers for signature verification var headers = Request.Headers.ToDictionary( h => h.Key, h => h.Value.ToString() ?? string.Empty ); // Get the signature header value (first available) var signatureHeader = headers.FirstOrDefault(h => h.Key.Contains("signature", StringComparison.OrdinalIgnoreCase)) .Value; // Process and verify the webhook var result = _webhookService.ProcessRequest(rawBody, signatureHeader, headers); if (!result.Success) { return StatusCode(result.StatusCode, new { error = result.Message }); } return Ok(new { status = "received", eventId = Guid.NewGuid().ToString(), timestamp = DateTime.UtcNow }); } /// <summary> /// Health check endpoint for webhook service. /// </summary> [HttpGet("health")] public IActionResult HealthCheck() { return Ok(new { status = "healthy", timestamp = DateTime.UtcNow, version = typeof(WebhookController).Assembly.GetName().Version.ToString() }); } /// <summary> /// Returns list of supported webhook events. /// </summary> [HttpGet("events")] public IActionResult GetSupportedEvents() { return Ok(new { events = new[] { "user.created", "user.deleted", "payment.completed", "order.shipped" } }); } } } ``` ### 3. **WebhookConfiguration.cs** - Dependency Injection Setup ```csharp using Microsoft.Extensions.DependencyInjection; using WebhookDelivery.Services; namespace WebhookDelivery.Configuration { /// <summary> /// Extension methods for configuring webhook services. /// </summary> public static class WebhookServiceCollectionExtensions { /// <summary> /// Adds webhook service to the DI container. /// </summary> public static IServiceCollection AddWebhookServices( this IServiceCollection services, Action<WebhookOptions>? configure = null) { var options = new WebhookOptions(); // Allow configuration via delegate or defaults configure?.Invoke(options); services.Configure<WebhookOptions>(options); services.AddSingleton<IWebhookService, WebhookService>(); return services; } /// <summary> /// Adds webhook controller to the DI container. /// </summary> public static IServiceCollection AddWebhookController( this IServiceCollection services) { services.AddControllers(options => { options.Filters.Add(new WebhookValidationFilter()); }); return services; } } /// <summary> /// Filter for additional webhook validation. /// </summary> public class WebhookValidationFilter : IAsyncActionFilter { private readonly ILogger<WebhookValidationFilter> _logger; public WebhookValidationFilter(ILogger<WebhookValidationFilter> logger) { _logger = logger; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // Additional validation before action execution if (!context.Request.Headers.ContainsKey("Content-Type")) { context.Result = new BadRequestObjectResult( new { error = "Missing Content-Type header" } ); return; } await next(); } } } ``` ### 4. **WebhookStartup.cs** - Application Configuration ```csharp using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using WebhookDelivery.Configuration; namespace WebhookDelivery { /// <summary> /// Startup configuration for webhook application. /// </summary> public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); } /// <summary> /// Application startup configuration. /// </summary> public class Startup { public void ConfigureServices(IServiceCollection services) { // Add webhook services with default configuration services.AddWebhookServices(options => { options.SecretKey = "your-secret-key-here"; options.MaxBodySize = 10 * 1024 * 1024; // 10MB }); // Add controller services.AddWebhookController(); // Add other services (logging, database, etc.) services.AddControllers(); } public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); // Health check endpoint endpoints.MapGet("/health", () => Ok(new { status = "healthy" })) .RunAsync(async context => { await context.Response.WriteAsync("OK"); }); }); } } } ``` ### 5. **WebhookTests.cs** - Unit Tests ```csharp using System; using System.Collections.Generic; using System.Text.Json; using Moq; using Xunit; using WebhookDelivery.Services; namespace WebhookDelivery.Tests { public class WebhookServiceTests { private readonly string _testSecret = "test-secret-key-12345"; [Fact] public void VerifySignature_ValidSignature_ReturnsTrue() { var payload = "{\"event\":\"user.created\",\"data\":{\"id\":1}}"; using (var hmac = new System.Security.Cryptography.HMACSHA256( Encoding.UTF8.GetBytes(_testSecret))) { var expectedHash = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(payload))); var service = new WebhookService(new Mock<IOptions<WebhookOptions>>().Object); // Note: In real code, secret is injected via constructor Assert.True(service.VerifySignature(payload, expectedHash, _testSecret)); } } [Fact] public void VerifySignature_InvalidSignature_ReturnsFalse() { var payload = "{\"event\":\"user.created\"}"; var service = new WebhookService(new Mock<IOptions<WebhookOptions>>().Object); Assert.False(service.VerifySignature(payload, "invalid-signature", _testSecret)); } [Fact] public void ProcessRequest_ValidRequest_ReturnsOk() { var payload = "{\"event\":\"user.created\",\"data\":{\"id\":1}}"; var signature = GenerateValidSignature(payload, _testSecret); var headers = new Dictionary<string, string> { ["X-Signature"] = signature }; var service = new WebhookService(new Mock<IOptions<WebhookOptions>>().Object); var result = service.ProcessRequest(payload, null, headers); Assert.True(result.Success); Assert.Equal(200, result.StatusCode); } [Fact] public void ProcessRequest_MissingSignature_ReturnsUnauthorized() { var payload = "{\"event\":\"user.created\"}"; var service = new WebhookService(new Mock<IOptions<WebhookOptions>>().Object); var result = service.ProcessRequest(payload, null, new Dictionary<string, string>()); Assert.False(result.Success); Assert.Equal(401, result.StatusCode); } [Fact] public void ProcessRequest_EmptyBody_ReturnsBadRequest() { var service = new WebhookService(new Mock<IOptions<WebhookOptions>>().Object); var result = service.ProcessRequest(string.Empty, null, new Dictionary<string, string>()); Assert.False(result.Success); Assert.Equal(400, result.StatusCode); } private static string GenerateValidSignature(string payload, string secret) { using (var hmac = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(secret))) { return Convert.ToHexString(hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payload))); } } } } ``` ## Usage Example ### appsettings.json Configuration ```json { "WebhookOptions": { "SecretKey": "your-secure-secret-key-here", "MaxBodySize": 10485760, "Timeout": "00:00:30" } } ``` ### Program.cs (ASP.NET Core 6+) ```csharp using WebhookDelivery.Configuration; var builder = WebApplication.CreateBuilder(args); // Configure webhook services builder.Services.AddWebhookServices(options => { options.SecretKey = builder.Configuration["WebhookOptions:SecretKey"]; }); // Add controllers builder.Services.AddWebhookController(); // Add other services... builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); ``` ## Key Features Summary | Feature | Description | |---------|-------------| | **HMAC-SHA256 Verification** | Industry-standard cryptographic signature verification | | **Flexible Header Support** | Supports multiple common signature header names | | **Configurable Limits** | Body size limits, timeout configuration | | **Type-Safe Models** | Strongly-typed payload and result objects | | **Dependency Injection** | Clean separation of concerns with DI | | **Comprehensive Logging** | Built-in logging hooks for audit trails | | **Unit Testable** | Fully testable verification logic | ## Security Considerations 1. **Never log secret keys** - Use environment variables or secure vaults 2. **Use HTTPS only** - Always enforce TLS for webhook endpoints 3. **Rate limiting** - Implement rate limits to prevent abuse 4. **Input validation** - Validate all incoming data before processing 5. **Fail securely** - Return appropriate

Enhanced response (with ZPI)

# Webhook Delivery Endpoint with HMAC Signature Verification ## Implementation ```csharp using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace WebhookDelivery { /// <summary> /// Configuration for webhook HMAC verification. /// </summary> public class WebhookConfig { /// <summary> /// The secret key used to generate and verify signatures. /// Should be stored securely in production (e.g., KeyVault, Environment Variables). /// </summary> public string SecretKey { get; set; } = ""; /// <summary> /// Header name for the expected signature (default: "X-Webhook-Signature"). /// </summary> public string SignatureHeaderName { get; set; } = "X-Webhook-Signature"; /// <summary> /// Whether to allow requests without a valid signature. /// Set to false in production for security. /// </summary> public bool AllowUnsignedRequests { get; set; } = false; /// <summary> /// Maximum allowed time difference between request and current time (for timestamp validation). /// </summary> public TimeSpan MaxTimestampSkew { get; set; } = TimeSpan.FromMinutes(5); } /// <summary> /// Interface for webhook payload handlers. Allows decoupling of verification logic from business processing. /// </summary> public interface IWebhookHandler { /// <summary> /// Processes the webhook payload after signature verification. /// </summary> Task ProcessPayloadAsync(WebhookPayload payload, CancellationToken cancellationToken); /// <summary> /// Returns a response to send back to the webhook sender. /// </summary> HttpResponseData GetResponse(); } /// <summary> /// Represents the verified and parsed webhook payload. /// </summary> public class WebhookPayload { public Dictionary<string, string> Headers { get; set; } = new(); public byte[] Body { get; set; } = Array.Empty<byte>(); public DateTime Timestamp { get; set; } internal static WebhookPayload Parse(IFormCollection form, string contentType) { var payload = new WebhookPayload(); // Parse headers from query parameters or body metadata if available foreach (var kvp in form) { payload.Headers[kvp.Key] = kvp.Value; } // Extract timestamp if present in headers if (payload.Headers.TryGetValue("timestamp", out var tsStr)) { if (DateTime.TryParse(tsStr, out var parsedTs)) { payload.Timestamp = parsedTs; } } return payload; } } /// <summary> /// Middleware for HMAC signature verification. /// </summary> public class WebhookSignatureMiddleware { private readonly RequestDelegate _next; private readonly ILogger<WebhookSignatureMiddleware> _logger; private readonly WebhookConfig _config; public WebhookSignatureMiddleware( RequestDelegate next, IOptions<WebhookConfig> config, ILogger<WebhookSignatureMiddleware> logger) { _next = next; _config = config.Value; _logger = logger; } public async Task InvokeAsync(HttpContext context, CancellationToken cancellationToken) { try { // Extract signature from header var providedSignature = context.Request.Headers[_config.SignatureHeaderName].FirstOrDefault(); if (string.IsNullOrEmpty(providedSignature)) { _logger.LogWarning("No webhook signature found in request headers."); return; // Allow unsigned requests if configured } // Calculate expected signature var bodyBytes = await context.Request.Body.ReadAllBytesAsync(cancellationToken); var calculatedSignature = CalculateHmacSha256(bodyBytes, _config.SecretKey); // Compare signatures (constant-time comparison to prevent timing attacks) bool isValid = VerifySignature(providedSignature, calculatedSignature); if (!isValid) { _logger.LogWarning("Invalid webhook signature detected."); context.Response.StatusCode = 401; await context.Response.WriteAsync("Invalid signature", Encoding.UTF8); return; } // Restore request body for downstream processing context.Request.Body = new MemoryStream(bodyBytes, writable: true); await _next(context, cancellationToken); } catch (Exception ex) when (!ex.IsCancellationRequested) { _logger.LogError(ex, "Error processing webhook request."); context.Response.StatusCode = 500; await context.Response.WriteAsync("Internal server error", Encoding.UTF8); } } /// <summary> /// Calculates HMAC-SHA256 signature for the given data. /// </summary> private static string CalculateHmacSha256(byte[] data, string key) { using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)); return Convert.ToHexString(hmac.ComputeHash(data)).ToLowerInvariant(); } /// <summary> /// Verifies two signatures using constant-time comparison. /// </summary> private static bool VerifySignature(string provided, string calculated) { if (string.IsNullOrEmpty(provided) || string.IsNullOrEmpty(calculated)) return false; // Use a secure comparison to prevent timing attacks var providedBytes = Encoding.UTF8.GetBytes(provided); var calculatedBytes = Encoding.UTF8.GetBytes(calculated); int length = Math.Min(providedBytes.Length, calculatedBytes.Length); bool equal = true; for (int i = 0; i < length; i++) { if (providedBytes[i] != calculatedBytes[i]) equal = false; } // If lengths differ, signatures are definitely not equal if (providedBytes.Length != calculatedBytes.Length) return false; return equal; } } /// <summary> /// Extension method to register webhook services in DI container. /// </summary> public static class WebhookServiceCollectionExtensions { /// <summary> /// Registers webhook configuration and middleware with scoped lifetime. /// </summary> public static IServiceCollection AddWebhookServices( this IServiceCollection services, Action<WebhookConfig> configure) { var config = new WebhookConfig(); configure(config); // Register configuration as Options (scoped by default in ASP.NET Core) services.Configure<WebhookConfig>(options => { options.SecretKey ??= Environment.GetEnvironmentVariable("WEBHOOK_SECRET_KEY"); if (string.IsNullOrEmpty(options.SecretKey)) throw new InvalidOperationException("WEBHOOK_SECRET_KEY environment variable must be set."); }); // Register middleware services.AddHttpContextAccessor(); services.AddScoped<WebhookSignatureMiddleware>(); return services; } /// <summary> /// Registers a webhook handler with scoped lifetime. /// </summary> public static IServiceCollection AddWebhookHandler<T>(this IServiceCollection services) where T : class, IWebhookHandler { services.AddScoped<IWebhookHandler, T>(); return services; } /// <summary> /// Configures the webhook middleware to be used in a pipeline. /// </summary> public static WebApplicationBuilder UseWebhookMiddleware( this WebApplicationBuilder builder) { var app = builder.Build(); // Add middleware after routing but before endpoint handling if (app.Services.TryGetRequiredService<ILoggerFactory>() is ILoggerFactory loggerFactory) { app.UseMiddleware<WebhookSignatureMiddleware>(); } return builder; } } } ``` ## Explanation ### Architecture Overview 1. **Configuration Layer** (`WebhookConfig`) - Stores the secret key, header name, and security options - Uses dependency injection via `IOptions<T>` pattern for easy configuration management - Supports environment variable fallback for production secrets 2. **Interface Layer** (`IWebhookHandler`, `WebhookPayload`) - Decouples verification from business logic - Allows multiple handlers to process different webhook types - Enables testing with mock implementations 3. **Middleware Layer** (`WebhookSignatureMiddleware`) - Intercepts requests before they reach controllers/endpoints - Validates HMAC signatures using SHA-256 - Implements constant-time comparison to prevent timing attacks - Logs warnings for debugging and monitoring 4. **Dependency Injection Extensions** - Registers middleware with scoped lifetime (appropriate for per-request state) - Provides fluent API for service registration - Handles configuration validation at startup ### Security Considerations - **HMAC-SHA256**: Industry-standard algorithm for webhook verification - **Constant-time comparison**: Prevents timing attacks that could leak information about valid signatures - **Secret key management**: Uses environment variables with clear error messages if missing - **Timestamp skew tolerance**: Configurable to handle clock drift between services ### Usage Example ```csharp // Program.cs or Startup.cs builder.Services.AddWebhookServices(config => { config.SecretKey = Environment.GetEnvironmentVariable("WEBHOOK_SECRET_KEY"); config.AllowUnsignedRequests = false; // Production setting }); builder.Services.AddWebhookHandler<MyWebhookHandler>(); var app = builder.Build(); app.UseWebhookMiddleware(); ```

Grader chain

Each grader reads the same baseline + enhanced pair and casts an independent verdict. Disagreement is the framework's own signal — not noise to be averaged away.

V1claude-sonnet-4-6
Better
V4qwen2.5-coder-7b-instruct
Better

Comments

Our grader said what it said. What do you say? Comment as a guest below.

No comments yet. Be the first to say what you make of this round.

← back to all rounds