C#
Verify webhooks
Check the signature of a delivery before you trust what it says.
Verify a delivery
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
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.