// Receive + verify a CodeB webhook in ASP.NET Core. // Events include call.started, call.answered, call.ended, transcript.saved, // call.ice.connected, call.transferred, outbound-ai.finished, // sfu.publisher.connected, sfu.subscriber.connected. // European Digital Identity Wallet sign-in events flow through the same // dispatcher; verification is identical. // [webhooks-cookbook 2026-07-23] using System.Security.Cryptography; using System.Text; var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); string SECRET = Environment.GetEnvironmentVariable("CODEB_WEBHOOK_SECRET") ?? throw new InvalidOperationException("set CODEB_WEBHOOK_SECRET"); app.MapPost("/webhook", async (HttpRequest req) => { // Read raw body -- HMAC is over the exact bytes. using var ms = new MemoryStream(); await req.Body.CopyToAsync(ms); byte[] raw = ms.ToArray(); string got = req.Headers["X-CodeB-Signature"].ToString(); using var h = new HMACSHA256(Encoding.UTF8.GetBytes(SECRET)); byte[] hash = h.ComputeHash(raw); string want = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant(); if (!CryptographicOperations.FixedTimeEquals( Encoding.ASCII.GetBytes(got), Encoding.ASCII.GetBytes(want))) return Results.Unauthorized(); string evt = req.Headers["X-CodeB-Event"].ToString(); string delId = req.Headers["X-CodeB-Delivery-Id"].ToString(); string payload = Encoding.UTF8.GetString(raw); Console.WriteLine($"[{delId}] {evt} {payload}"); // ... process asynchronously; ACK within 5s. return Results.Ok(); }); app.Run();