Securing webhooks in ASP.NET Core Minimal APIs: HMAC signature check and replay protection

ASP.NET Core Interview Q&A November 13, 2026 csharp

Verify the HMAC signature with a constant-time comparison, plus a timestamp (replay protection).

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ======================================================================
// Q: Webhook from the logistics provider: what do you check?
//
// Verify the HMAC signature with a constant-time comparison, plus a timestamp
// (replay protection). Dedupe by event id. Reply quickly with 202 and process
// asynchronously.
// ======================================================================

app.MapPost("/webhooks/shipping", async (HttpRequest req, IOptions<WebhookOptions> opt, ...) =>
{
    using var reader = new StreamReader(req.Body);
    var body = await reader.ReadToEndAsync();
    var signature = req.Headers["X-Signature"].ToString();

    var expected = Convert.ToHexString(
        HMACSHA256.HashData(Encoding.UTF8.GetBytes(opt.Value.Secret), Encoding.UTF8.GetBytes(body)));

    if (!CryptographicOperations.FixedTimeEquals(
            Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signature.ToUpperInvariant())))
        return Results.Unauthorized();

    // dedupe by event id, write to a queue, fast 202
    return Results.Accepted();
});