Webhook Verification

The SDK provides utilities to verify webhook signatures, ensuring that incoming webhook payloads genuinely originated from Felloh.


Verifying Signatures

Felloh signs every webhook request with an HMAC-SHA256 signature sent in the X-Signature header. The SDK provides two static methods on the WebhookVerifier class to verify this signature:

  • WebhookVerifier.VerifySignature — returns true or false
  • WebhookVerifier.AssertSignature — throws FellohWebhookSignatureException if invalid

Both use timing-safe comparison to prevent timing attacks. The methods accept either string or byte[] payloads.

Parameters

  • Name
    payloadrequired
    Type
    string | byte[]
    Description

    The raw request body. Must not be parsed or modified — use the raw body as received.

  • Name
    signaturerequired
    Type
    string
    Description

    The value of the X-Signature header from the webhook request.

  • Name
    secretrequired
    Type
    string
    Description

    Your webhook signing secret from the Felloh dashboard.

Boolean Check

using Felloh;

var isValid = WebhookVerifier.VerifySignature(
    payload: rawRequestBody,
    signature: request.Headers["X-Signature"],
    secret: "your-webhook-secret"
);

if (!isValid)
{
    return Results.Unauthorized();
}

// Process the webhook...

Assert (throws on failure)

using Felloh;
using Felloh.Errors;

try
{
    WebhookVerifier.AssertSignature(
        payload: rawRequestBody,
        signature: request.Headers["X-Signature"],
        secret: "your-webhook-secret"
    );
}
catch (FellohWebhookSignatureException)
{
    return Results.Unauthorized();
}

// Signature is valid, process the webhook...

ASP.NET Core Minimal API Example

When using ASP.NET Core Minimal APIs, read the raw request body for signature verification.

Minimal API Webhook Handler

using Felloh;
using Felloh.Errors;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/webhooks/felloh", async (HttpContext context) =>
{
    using var reader = new StreamReader(context.Request.Body);
    var body = await reader.ReadToEndAsync();

    try
    {
        WebhookVerifier.AssertSignature(
            payload: body,
            signature: context.Request.Headers["X-Signature"]!,
            secret: builder.Configuration["Felloh:WebhookSecret"]!
        );
    }
    catch (FellohWebhookSignatureException)
    {
        return Results.Unauthorized();
    }

    var payload = JsonSerializer.Deserialize<JsonElement>(body);
    Console.WriteLine($"Webhook received: {payload}");

    return Results.Ok();
});

app.Run();

ASP.NET Core MVC Example

When using ASP.NET Core MVC controllers, use Request.Body to read the raw request body.

MVC Controller Webhook Handler

using Felloh;
using Felloh.Errors;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("webhooks")]
public class WebhooksController : ControllerBase
{
    private readonly IConfiguration _config;

    public WebhooksController(IConfiguration config)
    {
        _config = config;
    }

    [HttpPost("felloh")]
    public async Task<IActionResult> HandleWebhook()
    {
        using var reader = new StreamReader(Request.Body);
        var body = await reader.ReadToEndAsync();

        try
        {
            WebhookVerifier.AssertSignature(
                payload: body,
                signature: Request.Headers["X-Signature"]!,
                secret: _config["Felloh:WebhookSecret"]!
            );
        }
        catch (FellohWebhookSignatureException)
        {
            return Unauthorized("Invalid signature");
        }

        // Process the webhook...
        return Ok();
    }
}