Round #349
08 May 2026, 17:09· csharp· claude-sonnet-4-6· r13-clean-hints
- Complexity
- band 0
- ZP / UO / OBS
- 5 / 4 / 1
- Journey weight
- 612
- Lightshift ΔL
- +0.17
Prompt
Implement a webhook delivery endpoint with HMAC signature verification
Zero-point index (what the engine surfaced)
ZPI-ZPI-0003 | 2026-05-08T17:09:33Z
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
## Project Structure
```
WebhookService/
├── Controllers/
│ └── WebhookController.cs
├── Services/
│ ├── WebhookSignatureService.cs
│ ├── WebhookDeliveryService.cs
│ └── WebhookRetryService.cs
├── Middleware/
│ └── WebhookSignatureMiddleware.cs
├── Models/
│ ├── WebhookEvent.cs
│ ├── WebhookEndpoint.cs
│ ├── WebhookDelivery.cs
│ └── WebhookResult.cs
├── Filters/
│ └── ValidateWebhookSignatureFilter.cs
└── Program.cs
```
## Models
```csharp
// Models/WebhookEvent.cs
using System.Text.Json.Serialization;
namespace WebhookService.Models;
public class WebhookEvent
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string EventType { get; set; } = string.Empty;
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
public int Version { get; set; } = 1;
public object Payload { get; set; } = new();
public Dictionary<string, string> Metadata { get; set; } = new();
}
public class WebhookPayload<T>
{
[JsonPropertyName("event_id")]
public string EventId { get; set; } = string.Empty;
[JsonPropertyName("event_type")]
public string EventType { get; set; } = string.Empty;
[JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; }
[JsonPropertyName("version")]
public int Version { get; set; }
[JsonPropertyName("data")]
public T? Data { get; set; }
}
```
```csharp
// Models/WebhookEndpoint.cs
namespace WebhookService.Models;
public class WebhookEndpoint
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; } = string.Empty;
public string Url { get; set; } = string.Empty;
public string Secret { get; set; } = string.Empty;
public List<string> EventTypes { get; set; } = new();
public bool IsActive { get; set; } = true;
public int TimeoutSeconds { get; set; } = 30;
public int MaxRetries { get; set; } = 3;
public Dictionary<string, string> Headers { get; set; } = new();
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? LastDeliveryAt { get; set; }
}
```
```csharp
// Models/WebhookDelivery.cs
namespace WebhookService.Models;
public enum DeliveryStatus
{
Pending,
InProgress,
Succeeded,
Failed,
Retrying,
Abandoned
}
public class WebhookDelivery
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string EndpointId { get; set; } = string.Empty;
public string EventId { get; set; } = string.Empty;
public string EventType { get; set; } = string.Empty;
public string Payload { get; set; } = string.Empty;
public string Signature { get; set; } = string.Empty;
public DeliveryStatus Status { get; set; } = DeliveryStatus.Pending;
public int AttemptCount { get; set; } = 0;
public int MaxAttempts { get; set; } = 3;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? LastAttemptAt { get; set; }
public DateTime? NextRetryAt { get; set; }
public DateTime? CompletedAt { get; set; }
public List<DeliveryAttempt> Attempts { get; set; } = new();
}
public class DeliveryAttempt
{
public int AttemptNumber { get; set; }
public DateTime AttemptedAt { get; set; } = DateTime.UtcNow;
public int? HttpStatusCode { get; set; }
public string? ResponseBody { get; set; }
public string? ErrorMessage { get; set; }
public long DurationMs { get; set; }
public bool Succeeded { get; set; }
}
```
```csharp
// Models/WebhookResult.cs
namespace WebhookService.Models;
public class WebhookResult
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
public string? DeliveryId { get; set; }
public string? EventId { get; set; }
public Dictionary<string, object> Details { get; set; } = new();
public static WebhookResult Ok(string message, string? deliveryId = null, string? eventId = null) =>
new() { Success = true, Message = message, DeliveryId = deliveryId, EventId = eventId };
public static WebhookResult Fail(string message) =>
new() { Success = false, Message = message };
}
public class SignatureVerificationResult
{
public bool IsValid { get; set; }
public string? FailureReason { get; set; }
public string? ReceivedSignature { get; set; }
public string? ComputedSignature { get; set; }
public DateTime VerifiedAt { get; set; } = DateTime.UtcNow;
public static SignatureVerificationResult Valid(string received, string computed) =>
new() { IsValid = true, ReceivedSignature = received, ComputedSignature = computed };
public static SignatureVerificationResult Invalid(string reason, string? received = null, string? computed = null) =>
new() { IsValid = false, FailureReason = reason, ReceivedSignature = received, ComputedSignature = computed };
}
```
## Signature Service
```csharp
// Services/WebhookSignatureService.cs
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Options;
using WebhookService.Models;
namespace WebhookService.Services;
public interface IWebhookSignatureService
{
string ComputeSignature(string payload, string secret, string? timestamp = null);
SignatureVerificationResult VerifySignature(string payload, string signature, string secret, string? timestamp = null);
string GenerateSecret(int length = 32);
bool IsTimestampValid(string timestamp, int toleranceSeconds = 300);
}
public class WebhookSignatureOptions
{
public string SignatureHeader { get; set; } = "X-Webhook-Signature";
public string TimestampHeader { get; set; } = "X-Webhook-Timestamp";
public string SignaturePrefix { get; set; } = "sha256=";
public int TimestampToleranceSeconds { get; set; } = 300; // 5 minutes
public bool RequireTimestamp { get; set; } = true;
}
public class WebhookSignatureService : IWebhookSignatureService
{
private readonly WebhookSignatureOptions _options;
private readonly ILogger<WebhookSignatureService> _logger;
public WebhookSignatureService(
IOptions<WebhookSignatureOptions> options,
ILogger<WebhookSignatureService> logger)
{
_options = options.Value;
_logger = logger;
}
/// <summary>
/// Computes HMAC-SHA256 signature for the given payload.
/// When timestamp is provided, signs "timestamp.payload" to prevent replay attacks.
/// </summary>
public string ComputeSignature(string payload, string secret, string? timestamp = null)
{
ArgumentException.ThrowIfNull
Enhanced response (with ZPI)
# Webhook Delivery Endpoint with HMAC Signature Verification
## Project Setup
Install the required packages:
```bash
dotnet add package Microsoft.AspNetCore.App
```
---
## Models
```csharp
// Models/WebhookPayload.cs
namespace WebhookReceiver.Models;
public sealed record WebhookPayload(
string EventType,
string EventId,
DateTimeOffset OccurredAt,
JsonElement Data
);
public sealed record WebhookDeliveryResult(
bool Success,
string Message,
string EventId
);
```
---
## HMAC Signature Verification Service
```csharp
// Services/IWebhookSignatureVerifier.cs
namespace WebhookReceiver.Services;
public interface IWebhookSignatureVerifier
{
ValueTask<bool> VerifyAsync(string? signatureHeader, ReadOnlyMemory<byte> body, CancellationToken ct = default);
}
```
```csharp
// Services/WebhookSignatureVerifier.cs
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Options;
using WebhookReceiver.Configuration;
namespace WebhookReceiver.Services;
public sealed class WebhookSignatureVerifier : IWebhookSignatureVerifier
{
private const string SignaturePrefix = "sha256=";
private readonly WebhookOptions _options;
private readonly ILogger<WebhookSignatureVerifier> _logger;
public WebhookSignatureVerifier(
IOptions<WebhookOptions> options,
ILogger<WebhookSignatureVerifier> logger)
{
_options = options.Value;
_logger = logger;
}
public ValueTask<bool> VerifyAsync(
string? signatureHeader,
ReadOnlyMemory<byte> body,
CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(signatureHeader))
{
_logger.LogWarning("Webhook request rejected: missing signature header.");
return ValueTask.FromResult(false);
}
if (!signatureHeader.StartsWith(SignaturePrefix, StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning(
"Webhook request rejected: unrecognised signature format '{Header}'.",
signatureHeader);
return ValueTask.FromResult(false);
}
var providedHex = signatureHeader[SignaturePrefix.Length..];
try
{
var providedBytes = Convert.FromHexString(providedHex);
var secretBytes = Encoding.UTF8.GetBytes(_options.SigningSecret);
var computedBytes = HMACSHA256.HashData(secretBytes, body.Span);
// Constant-time comparison to prevent timing attacks
var match = CryptographicOperations.FixedTimeEquals(providedBytes, computedBytes);
if (!match)
{
_logger.LogWarning("Webhook request rejected: signature mismatch.");
}
return ValueTask.FromResult(match);
}
catch (FormatException ex)
{
_logger.LogWarning(ex, "Webhook request rejected: signature is not valid hex.");
return ValueTask.FromResult(false);
}
}
}
```
---
## Replay Attack Prevention
```csharp
// Services/IWebhookReplayGuard.cs
namespace WebhookReceiver.Services;
public interface IWebhookReplayGuard
{
/// <summary>Returns false if the event ID has already been processed.</summary>
ValueTask<bool> TryMarkSeenAsync(string eventId, CancellationToken ct = default);
}
```
```csharp
// Services/InMemoryWebhookReplayGuard.cs
using System.Collections.Concurrent;
using Microsoft.Extensions.Options;
using WebhookReceiver.Configuration;
namespace WebhookReceiver.Services;
/// <summary>
/// Production note: replace with a distributed cache (Redis SETNX) so that
/// replay protection works across multiple instances.
/// </summary>
public sealed class InMemoryWebhookReplayGuard : IWebhookReplayGuard
{
private readonly ConcurrentDictionary<string, DateTimeOffset> _seen = new();
private readonly WebhookOptions _options;
private readonly ILogger<InMemoryWebhookReplayGuard> _logger;
public InMemoryWebhookReplayGuard(
IOptions<WebhookOptions> options,
ILogger<InMemoryWebhookReplayGuard> logger)
{
_options = options.Value;
_logger = logger;
}
public ValueTask<bool> TryMarkSeenAsync(string eventId, CancellationToken ct = default)
{
PurgeExpired();
var added = _seen.TryAdd(eventId, DateTimeOffset.UtcNow);
if (!added)
{
_logger.LogWarning("Duplicate webhook event '{EventId}' rejected.", eventId);
}
return ValueTask.FromResult(added);
}
private void PurgeExpired()
{
var cutoff = DateTimeOffset.UtcNow - _options.ReplayWindow;
foreach (var (key, ts) in _seen)
{
if (ts < cutoff)
{
_seen.TryRemove(key, out _);
}
}
}
}
```
---
## Event Processor
```csharp
// Services/IWebhookEventProcessor.cs
namespace WebhookReceiver.Services;
public interface IWebhookEventProcessor
{
Task ProcessAsync(WebhookPayload payload, CancellationToken ct = default);
}
```
```csharp
// Services/WebhookEventProcessor.cs
using WebhookReceiver.Models;
namespace WebhookReceiver.Services;
public sealed class WebhookEventProcessor : IWebhookEventProcessor
{
private readonly ILogger<WebhookEventProcessor> _logger;
public WebhookEventProcessor(ILogger<WebhookEventProcessor> logger)
=> _logger = logger;
public Task ProcessAsync(WebhookPayload payload, CancellationToken ct = default)
{
// Dispatch to the appropriate handler by event type
return payload.EventType switch
{
"order.created" => HandleOrderCreatedAsync(payload, ct),
"payment.settled" => HandlePaymentSettledAsync(payload, ct),
_ => HandleUnknownEventAsync(payload, ct)
};
}
private Task HandleOrderCreatedAsync(WebhookPayload payload, CancellationToken ct)
{
_logger.LogInformation(
"Processing order.created event {EventId} occurred at {OccurredAt}.",
payload.EventId, payload.OccurredAt);
// TODO: real business logic
return Task.CompletedTask;
}
private Task HandlePaymentSettledAsync(WebhookPayload payload, CancellationToken ct)
{
_logger.LogInformation(
"Processing payment.settled event {EventId}.",
payload.EventId);
// TODO: real business logic
return Task.CompletedTask;
}
private Task HandleUnknownEventAsync(WebhookPayload payload, CancellationToken ct)
{
_logger.LogWarning(
"Received unknown event type '{EventType}' (id={EventId}). Ignored.",
payload.EventType, payload.EventId);
return Task.CompletedTask;
}
}
```
---
## Configuration
```csharp
// Configuration/WebhookOptions.cs
using System.ComponentModel.DataAnnotations;
namespace WebhookReceiver.Configuration;
public sealed class WebhookOptions
{
public const string SectionName = "Webhook";
[Required, MinLength(32)]
public string SigningSecret { get; init; } = string.Empty;
/// <summary>Reject events older than this window (replay protection).</summary>
public TimeSpan ReplayWindow { get; init; } =
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
BetterV4qwen2.5-coder-7b-instruct
BetterComments
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.