Round #285

08 May 2026, 12:31· csharp· openai/gpt-oss-120b:free· 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-08T12:31:54Z 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)

Below is a **complete, production‑ready** example of a webhook‑delivery endpoint that 1. **Receives** a POST request containing a JSON payload. 2. **Validates** an HMAC‑SHA256 signature that the sender (the webhook provider) adds in an HTTP header. 3. **Returns** the appropriate HTTP status codes (`200 OK` on success, `401 Unauthorized` on signature failure, `400 Bad Request` on malformed payload, etc.). 4. **Is easy to test** with tools like `curl` or Postman. The implementation is shown for three common ASP.NET Core hosting models: | Scenario | Where the code lives | Why you might choose it | |----------|----------------------|------------------------| | **Minimal API** (ASP.NET Core 6+) | `Program.cs` | Small services, micro‑functions, or when you want the least ceremony. | | **Controller‑based API** (ASP.NET Core MVC) | `Controllers/WebhookController.cs` | Larger apps that already use MVC, attribute routing, filters, etc. | | **Middleware** (global verification) | `Middleware/HmacVerificationMiddleware.cs` | When *all* incoming requests to a path (or the whole app) must be verified before they reach any controller. | --- ## 1. Shared secret configuration All three approaches read a **shared secret** (the key used to compute the HMAC) from configuration – e.g. `appsettings.json`, environment variables, Azure Key Vault, etc. ```json // appsettings.json { "Webhook": { "Secret": "YOUR_SUPER_SECRET_KEY_BASE64" // store as base64 or plain text (but protect!) } } ``` > **Security tip** – Never commit the secret to source control. Use environment variables or a secret manager in production. --- ## 2. Helper class – HMAC verification logic Put the verification code in a reusable static helper (or a service if you prefer DI). It does **constant‑time** comparison to avoid timing attacks. ```csharp // Services/HmacValidator.cs using System; using System.Security.Cryptography; using System.Text; namespace MyWebhookApp.Services { public static class HmacValidator { /// <summary> /// Validates that the supplied signature matches the HMAC‑SHA256 of the request body. /// </summary> /// <param name="secret">The shared secret (raw bytes).</param> /// <param name="payload">The raw request body (bytes).</param> /// <param name="providedSignature">Signature string from the header (e.g. "sha256=abcd...").</param> /// <returns>True if the signature is valid; otherwise false.</returns> public static bool IsValid(byte[] secret, byte[] payload, string? providedSignature) { if (string.IsNullOrWhiteSpace(providedSignature)) return false; // Expected format: "sha256=hexstring" const string prefix = "sha256="; if (!providedSignature.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return false; var hexSignature = providedSignature[prefix.Length..]; if (hexSignature.Length % 2 != 0) return false; // malformed hex byte[] signatureBytes; try { signatureBytes = Convert.FromHexString(hexSignature); } catch (FormatException) { return false; } // Compute HMAC‑SHA256 of the payload using var hmac = new HMACSHA256(secret); var computedHash = hmac.ComputeHash(payload); // Constant‑time comparison return CryptographicOperations.FixedTimeEquals(computedHash, signatureBytes); } /// <summary> /// Helper to decode the secret from configuration (base64 or plain text). /// </summary> public static byte[] DecodeSecret(string secretFromConfig) { // If the secret looks like base64, decode it; otherwise treat it as UTF‑8 bytes. try { return Convert.FromBase64String(secretFromConfig); } catch (FormatException) { return Encoding.UTF8.GetBytes(secretFromConfig); } } } } ``` --- ## 3. Minimal API implementation (ASP.NET Core 6+) ```csharp // Program.cs using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MyWebhookApp.Services; var builder = WebApplication.CreateBuilder(args); // Bind the secret once at startup var webhookSecret = builder.Configuration.GetSection("Webhook:Secret").Value ?? throw new InvalidOperationException("Webhook secret not configured"); var secretBytes = HmacValidator.DecodeSecret(webhookSecret); var app = builder.Build(); app.MapPost("/webhook", async (HttpRequest request) => { // 1️⃣ Read the raw body (do NOT use model binding before verification) using var ms = new MemoryStream(); await request.Body.CopyToAsync(ms); var payloadBytes = ms.ToArray(); // 2️⃣ Grab the signature header (the name is provider‑specific; here we use "X-Hub-Signature-256") var signatureHeader = request.Headers["X-Hub-Signature-256"].FirstOrDefault(); // 3️⃣ Verify if (!HmacValidator.IsValid(secretBytes, payloadBytes, signatureHeader)) { return Results.Unauthorized(); // 401 } // 4️⃣ At this point the payload is trusted – deserialize it // (replace `MyWebhookPayload` with your own POCO) var payload = System.Text.Json.JsonSerializer.Deserialize<MyWebhookPayload>(payloadBytes); if (payload is null) { return Results.BadRequest("Invalid JSON payload"); } // 5️⃣ Do whatever you need with the payload // e.g. enqueue a background job, update DB, etc. // For demo purposes we just echo it back: return Results.Ok(new { received = true, payload }); }) .Accepts<MyWebhookPayload>("application/json") .Produces(200) .Produces(401) .Produces(400); app.Run(); // --------------------------------------------------- // Example POCO for the payload (adjust fields as needed) public record MyWebhookPayload { public string Event { get; init; } = default!; public DateTime Timestamp { get; init; } public JsonElement Data { get; init; } // use JsonElement for dynamic payloads } ``` ### How to test (curl) ```bash # Sample payload payload='{"event":"order.created","timestamp":"2024-05-01T12:34:56Z","data":{"orderId":123}}' # Compute HMAC‑SHA256 using the same secret (replace with your secret) secret='YOUR_SUPER_SECRET_KEY_BASE64' # same as in appsettings # If secret is plain text, just echo -n "$secret" key=$(echo -n "$secret" | base64 -d 2>/dev/null || echo -n "$secret") signature=$(echo -n "$payload" | openssl dgst -sha256 -hmac "$key" -binary | xxd -p -c 256) header="sha256=$signature" curl -X POST http://localhost:5000/webhook \ -H "Content-Type: application/json" \ -H "X-Hub-Signature-256: $header" \ -d "$payload" ``` You should receive a JSON response with `"received": true`. --- ## 4. Controller‑based implementation (ASP.NET Core MVC) ```csharp // Controllers/WebhookController.cs using Microsoft.AspNetCore.Mvc; using MyWebhookApp.Services; namespace MyWebhookApp.Controllers; [ApiController] [Route("[controller]")] // POST /webhook public class WebhookController : ControllerBase { private readonly byte[] _secret; public WebhookController(IConfiguration configuration) { var secretString = configuration.GetValue<string>("Webhook:Secret") ?? throw new InvalidOperationException("Webhook secret missing"); _secret = HmacValidator.DecodeSecret(secretString); } [HttpPost] public async Task<IActionResult> Receive() { // 1️⃣ Read raw body using var ms = new MemoryStream(); await Request.Body.CopyToAsync(ms); var payloadBytes = ms.ToArray(); // 2️⃣ Header (adjust name if your provider uses a different one) var signatureHeader = Request.Headers["X-Hub-Signature-256"].FirstOrDefault(); // 3️⃣ Verify if (!HmacValidator.IsValid(_secret, payloadBytes, signatureHeader)) { return Unauthorized(); // 401 } // 4️⃣ Deserialize (you can also use System.Text.Json directly) MyWebhookPayload? payload; try { payload = System.Text.Json.JsonSerializer.Deserialize<MyWebhookPayload>(payloadBytes); } catch (System.Text.Json.JsonException) { return BadRequest("Invalid JSON"); } if (payload is null) return BadRequest("Empty payload"); // 5️⃣ Process payload (business logic) // Example: log it // _logger.LogInformation("Webhook received: {Event}", payload.Event); return Ok(new { status = "ok" }); } } ``` Add the controller to the pipeline (default in ASP.NET Core MVC templates): ```csharp // Program.cs (or Startup.cs for older versions) var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); // <-- make sure this is called var app = builder.Build(); app.MapControllers(); // <-- route to controllers app.Run(); ``` --- ## 5. Global middleware approach If **every** request to a certain path (or the whole API) must be signed, you can place the verification in a middleware component. This keeps controllers clean. ```csharp // Middleware/HmacVerificationMiddleware.cs using Microsoft.AspNetCore.Http; using MyWebhookApp.Services; using System.Threading.Tasks; namespace MyWebhookApp.Middleware; public class HmacVerificationMiddleware { private readonly RequestDelegate _next; private readonly byte[] _secret; private readonly string _signatureHeaderName; public HmacVerificationMiddleware(RequestDelegate next, IConfiguration config, string signatureHeaderName = "X-Hub-Signature-256") { _next = next; var secretString = config.GetValue<string>("Webhook:Secret") ?? throw new InvalidOperationException("Webhook secret missing"); _secret = HmacValidator.DecodeSecret(secretString); _signatureHeaderName = signatureHeaderName; } public async Task InvokeAsync(HttpContext context) { // Only verify POST/PUT/PATCH (adjust as needed) if (!HttpMethods.IsPost(context.Request.Method) && !HttpMethods.IsPut(context.Request.Method) && !HttpMethods.IsPatch(context.Request.Method)) { await _next(context); return; } // Buffer the request body so we can read it multiple times context.Request.EnableBuffering(); using var ms = new MemoryStream(); await context.Request.Body.CopyToAsync(ms); var payload = ms.ToArray(); // Reset the stream position for downstream components context.Request.Body.Position = 0; var signatureHeader = context.Request.Headers[_signatureHeaderName].FirstOrDefault(); if (!HmacValidator.IsValid(_secret, payload, signatureHeader)) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; await context.Response.WriteAsync("Invalid HMAC signature"); return; } // Signature ok – continue down the pipeline await _next(context); } } ``` Register the middleware (order matters – it must run **before** MVC): ```csharp // Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); var app = builder.Build(); // Apply only to /webhook/* paths (optional) app.UseWhen( ctx => ctx.Request.Path.StartsWithSegments("/webhook"), appBuilder => appBuilder.UseMiddleware<MyWebhookApp.Middleware.HmacVerificationMiddleware>() ); app.MapControllers(); app.Run(); ``` Now your controller can assume the request is already verified and can use normal model binding: ```csharp [HttpPost] public IActionResult Receive([FromBody] MyWebhookPayload payload) { // No signature checks here – middleware already did it // Process payload... return Ok(); } ``` --- ## 6. Full project structure (suggested) ``` MyWebhookApp/ │ ├─ Controllers/ │ └─ WebhookController.cs │ ├─ Middleware/ │ └─ HmacVerificationMiddleware.cs │ ├─ Services/ │ └─ HmacValidator.cs │ ├─ Models/ │ └─ MyWebhookPayload.cs │ ├─ appsettings.json ├─ Program.cs └─ MyWebhookApp.csproj ``` --- ## 7. Testing strategies | Test type | How to perform | |-----------|----------------| | **Unit test** for `HmacValidator.IsValid` | Use xUnit/NUnit, feed known payload + secret + expected signature, assert true/false. | | **Integration test** (ASP.NET Core TestServer) | Spin up `WebApplicationFactory`, send a request with a valid/invalid signature, assert status codes. | | **Load test** | Use `k6` or `ab` to hit the endpoint with a realistic payload size; ensure the HMAC verification does not become a bottleneck. | | **Security test** | Verify that timing differences between valid/invalid signatures are negligible (the `FixedTimeEquals` call guarantees this). | --- ## 8. Common pitfalls & how we avoided them | Pitfall | What we did | |---------|--------------| | **Reading the body after model binding** – the stream is already consumed. | We read the raw body **first**, then reset `Request.Body.Position = 0` (or use `EnableBuffering`) before any further processing. | | **Using string comparison for signatures** – vulnerable to timing attacks. | Used `CryptographicOperations.FixedTimeEquals`. | | **Hard‑coding the secret** – leaks in source control. | Secret is read from configuration; you can replace it with Azure Key Vault, AWS Secrets Manager, etc. | | **Assuming the header is always present** – leads to `NullReferenceException`. | Guarded with null checks and returned `401` when missing/invalid. | | **Incorrect header format** – some providers send `sha1=` or no prefix. | The validator expects the `sha256=` prefix; you can easily adapt the code for other algorithms. | | **Base64 vs plain‑text secret** – mismatched encoding causes false failures. | `DecodeSecret` tries base64 first, then falls back to UTF‑8 bytes. | --- ## 9. Quick “copy‑paste” starter (Minimal API) If you just need a single file to drop into a new project: ```csharp // Program.cs using System.Security.Cryptography; using System.Text; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; var builder = WebApplication.CreateBuilder(args); var secretString = builder.Configuration.GetValue<string>("Webhook:Secret") ?? throw new InvalidOperationException("Webhook secret missing"); byte[] secret = DecodeSecret(secretString); var app = builder.Build(); app.MapPost("/webhook", async (HttpRequest req) => { using var ms = new MemoryStream(); await req.Body.CopyToAsync(ms); var payload = ms.ToArray(); var signature = req.Headers["X-Hub-Signature-256"].FirstOrDefault(); if (!IsValid(secret, payload, signature)) return Results.Unauthorized(); // trusted payload – deserialize as needed var json = Encoding.UTF8.GetString(payload); return Results.Ok(new { ok = true, received = json }); }); app.Run(); static byte[] DecodeSecret(string s) => Convert.TryFromBase64String(s, out var bytes) ? bytes : Encoding.UTF8.GetBytes(s); static bool IsValid(byte[] secret, byte[] payload, string? header) { if (string.IsNullOrEmpty(header) || !header.StartsWith("sha256=", StringComparison.OrdinalIgnoreCase)) return false; var hex = header["sha256=".Length..]; if (hex.Length % 2 != 0) return false; byte[] provided; try { provided = Convert.FromHexString(hex); } catch { return false; } using var hmac = new HMACSHA256(secret); var computed = hmac.ComputeHash(payload); return CryptographicOperations.FixedTimeEquals(computed, provided); } ``` Add the secret to `appsettings.json` (or an environment variable) and you’re ready to go. --- ### 🎉 You now have a fully functional, secure webhook endpoint in C# for ASP.NET Core, with three interchangeable patterns, a reusable HMAC validator, and guidance on testing and deployment. Happy coding!

Enhanced response (with ZPI)

**Solution Overview** We will create a small ASP.NET Core Web API that exposes a single **POST** endpoint `/api/webhook` which can be used by any external system that sends JSON payloads with an HMAC‑SHA256 signature. * The signature is expected in the request header `X‑Signature`. * The secret key used to compute the HMAC is stored in configuration (`appsettings.json`) and injected via a scoped service (`IWebhookValidator`). * The validator recomputes the HMAC over the raw request body and compares it with the header value using a time‑constant comparison. * If the signature is valid the payload is passed to a downstream handler (`IWebhookProcessor`). * All services are registered as **scoped** and `ILogger<T>` is injected wherever logging is required. The code below is a complete, production‑ready example that can be dropped into a new ASP.NET Core project (targeting .NET 8, but works on .NET 6+). --- ## 1. Domain model / contracts ```csharp // Represents the JSON payload that the webhook sends. // Adjust the properties to match the actual contract you expect. public sealed record WebhookPayload { public string Event { get; init; } = default!; public DateTimeOffset Timestamp { get; init; } public JsonElement Data { get; init; } // keep it flexible } ``` --- ## 2. HMAC verification service ```csharp using System.Security.Cryptography; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; public interface IWebhookValidator { /// <summary> /// Validates the HMAC signature of a request body. /// </summary> /// <param name="body">Raw request body (UTF‑8 bytes).</param> /// <param name="signatureHeader">Value of the X‑Signature header.</param> /// <returns>True if the signature matches the secret; otherwise false.</returns> Task<bool> IsValidAsync(byte[] body, string? signatureHeader); } public sealed class WebhookValidator : IWebhookValidator { private readonly ILogger<WebhookValidator> _logger; private readonly string _secret; public WebhookValidator(IOptions<WebhookOptions> options, ILogger<WebhookValidator> logger) { _logger = logger; _secret = options.Value.Secret ?? throw new InvalidOperationException("Webhook secret is not configured."); } public Task<bool> IsValidAsync(byte[] body, string? signatureHeader) { if (string.IsNullOrWhiteSpace(signatureHeader)) { _logger.LogWarning("Missing signature header."); return Task.FromResult(false); } // Expected format: "sha256=abcdef..." const string prefix = "sha256="; if (!signatureHeader.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { _logger.LogWarning("Signature header does not start with expected prefix."); return Task.FromResult(false); } var providedSignature = signatureHeader[prefix.Length..]; // Compute HMAC‑SHA256 of the request body using the secret. using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_secret)); var computedHash = hmac.ComputeHash(body); var computedSignature = Convert.ToHexString(computedHash).ToLowerInvariant(); // Time‑constant comparison to avoid timing attacks. var isValid = CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(computedSignature), Encoding.UTF8.GetBytes(providedSignature)); if (!isValid) { _logger.LogWarning("Invalid HMAC signature. Expected {Expected}, received {Received}.", computedSignature, providedSignature); } return Task.FromResult(isValid); } } ``` **Configuration POCO** ```csharp public sealed class WebhookOptions { public string? Secret { get; set; } } ``` Add the section to *appsettings.json*: ```json { "Webhook": { "Secret": "YOUR_SUPER_SECRET_KEY" } } ``` --- ## 3. Payload processing service (business logic) ```csharp public interface IWebhookProcessor { Task ProcessAsync(WebhookPayload payload, CancellationToken ct = default); } public sealed class WebhookProcessor : IWebhookProcessor { private readonly ILogger<WebhookProcessor> _logger; public WebhookProcessor(ILogger<WebhookProcessor> logger) { _logger = logger; } public Task ProcessAsync(WebhookPayload payload, CancellationToken ct = default) { // Example: just log the payload. Replace with real handling (store, trigger, etc.). _logger.LogInformation("Received webhook event {Event} at {Timestamp}. Data: {Data}", payload.Event, payload.Timestamp, payload.Data.GetRawText()); // Simulate async work. return Task.CompletedTask; } } ``` --- ## 4. The controller / endpoint ```csharp using Microsoft.AspNetCore.Mvc; using System.Text.Json; [ApiController] [Route("api/[controller]")] public sealed class WebhookController : ControllerBase { private readonly IWebhookValidator _validator; private readonly IWebhookProcessor _processor; private readonly ILogger<WebhookController> _logger; public WebhookController(IWebhookValidator validator, IWebhookProcessor processor, ILogger<WebhookController> logger) { _validator = validator; _processor = processor; _logger = logger; } [HttpPost] public async Task<IActionResult> ReceiveAsync(CancellationToken ct) { // 1️⃣ Read raw body (must be done before model binding) await using var ms = new MemoryStream(); await Request.Body.CopyToAsync(ms, ct); var bodyBytes = ms.ToArray(); // 2️⃣ Verify HMAC var signatureHeader = Request.Headers["X-Signature"].FirstOrDefault(); if (!await _validator.IsValidAsync(bodyBytes, signatureHeader)) { _logger.LogWarning("Webhook request rejected due to invalid signature."); return Unauthorized(); // 401 } // 3️⃣ Deserialize payload (JSON) WebhookPayload? payload; try { payload = JsonSerializer.Deserialize<WebhookPayload>(bodyBytes, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } catch (JsonException ex) { _logger.LogError(ex, "Failed to deserialize webhook payload."); return BadRequest("Invalid JSON payload."); } if (payload is null) { _logger.LogWarning("Deserialized payload is null."); return BadRequest("Empty payload."); } // 4️⃣ Process the payload await _processor.ProcessAsync(payload, ct); // 5️⃣ Respond with 202 Accepted (asynchronous processing) return Accepted(); } } ``` **Why we read the raw body manually** Model binding would consume the request stream, making it impossible to compute the HMAC over the exact bytes that the sender signed. By copying the stream into a `MemoryStream` we keep the original bytes for verification and then deserialize from the same byte array. --- ## 5. DI registration (Program.cs) ```csharp var builder = WebApplication.CreateBuilder(args); // 1️⃣ Configuration builder.Services.Configure<WebhookOptions>(builder.Configuration.GetSection("Webhook")); // 2️⃣ Scoped services (as requested) builder.Services.AddScoped<IWebhookValidator, WebhookValidator>(); builder.Services.AddScoped<IWebhookProcessor, WebhookProcessor>(); // 3️⃣ Controllers / minimal API builder.Services.AddControllers(); // 4️⃣ Logging is already set up by default (ILogger<T> injection works out‑of‑the‑box) // Build and run var app = builder.Build(); app.MapControllers(); app.Run(); ``` All services are **scoped**, meaning a new instance is created per HTTP request, which is ideal for request‑specific data (e.g., the secret is read once per request, and the logger is request‑scoped). --- ## 6. End‑to‑end test (optional) You can test the endpoint with `curl`: ```bash payload='{"event":"order.created","timestamp":"2024-05-08T12:34:56Z","data":{"orderId":123}}' secret='YOUR_SUPER_SECRET_KEY' # Compute HMAC signature=$(printf "%s" "$payload" | openssl dgst -sha256 -hmac "$secret" -binary | xxd -p -c 256) signature="sha256=$signature" curl -X POST http://localhost:5000/api/webhook \ -H "Content-Type: application/json" \ -H "X-Signature: $signature" \ -d "$payload" ``` If the secret matches, the response will be `202 Accepted` and the server logs will show the received event. --- ## 7. Summary of key points | Concern | Implementation | |---------|----------------| | **Routing middleware** | `app.MapControllers()` + `[ApiController]` attribute routes the POST to `/api/webhook`. | | **ILogger\<T> injection** | All services (`WebhookValidator`, `WebhookProcessor`, `WebhookController`) receive `ILogger<T>` via constructor injection. | | **Scoped registration** | `AddScoped` is used for `IWebhookValidator` and `IWebhookProcessor`. | | **HMAC verification** | `WebhookValidator` computes SHA‑256 HMAC over the raw request body and uses `CryptographicOperations.FixedTimeEquals` for a timing‑attack‑safe comparison. | | **Error handling** | Missing/invalid signature → `401 Unauthorized`; JSON deserialization errors → `400 Bad Request`; successful processing → `202 Accepted`. | | **Configuration** | Secret stored in `appsettings.json` under `Webhook:Secret` and bound to `WebhookOptions`. | This completes a production‑grade webhook delivery endpoint with secure HMAC signature verification, proper logging, and scoped DI registration.

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-haiku-4-5-20251001
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