Skip to the documentation
C#

Verify webhooks

Check the signature of a delivery before you trust what it says.

Verify a delivery

Program.cs
using OpenEmail; var builder = WebApplication.CreateBuilder(args);var app = builder.Build();var secret = builder.Configuration["OPENEMAIL_WEBHOOK_SECRET"] ?? string.Empty; app.MapPost("/webhooks/openemail", async (HttpRequest request) =>{    using var reader = new StreamReader(request.Body);    var payload = await reader.ReadToEndAsync();     try    {        var delivery = OpenEmailClient.VerifyWebhookSignature(payload, request.Headers["X-OpenEmail-Signature"], secret);         app.Logger.LogInformation("{Type} {Id}", (string?)delivery["type"], (string?)delivery["id"]);         return Results.NoContent();    }    catch (OpenEmailWebhookException)    {        return Results.BadRequest();    }}); app.Run();

OpenEmailClient.VerifyWebhookSignature() checks the HMAC of the header over the timestamp and the body in constant time, and returns the event as a JsonObject. The second argument is the value of the X-OpenEmail-Signature header, and other overloads take the request headers themselves. The secret is the one client.Webhooks.CreateAsync() returned.

Pass the raw body, exactly as it arrived. A body that was parsed and encoded again no longer matches its signature.

Old deliveries

tolerance.cs
var payload = """{"id":"evt_1","type":"email.received"}"""; try{    OpenEmailClient.VerifyWebhookSignature(payload, "t=1767225600,v1=5f2d", "whsec_example", toleranceSeconds: 60);}catch (OpenEmailWebhookException error){    Console.WriteLine($"refused: {error.Message}");}

A delivery more than five minutes old is refused, so a captured request cannot be replayed later. toleranceSeconds: changes the five minutes, and zero accepts a delivery of any age.

  • A missing or malformed header, a signature that does not match and a delivery that is too old all throw OpenEmailWebhookException.
  • After a secret is rotated, a delivery can carry more than one signature, and any one that matches passes.
  • Answer with a 2xx status quickly. A delivery that gets any other answer is tried again later.