[{"data":1,"prerenderedAt":112},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fsdks\u002Fdotnet":3},{"page":4,"toc":103,"updated":111},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fsdks\u002Fdotnet",".NET",".NET SDK: Read Events and Verify Webhooks in C#","Read identification events with a secret key and verify signed webhooks from .NET 6 and newer, with async APIs and dependency injection.",[10,13,18,23,26,37,40,42,47,49,52,54,58,61,63,67,70,75,78,101],{"type":11,"text":12},"p","The `Fingerly` NuGet package reads stored events by request ID and verifies webhook signatures. Every call is asynchronous and accepts a `CancellationToken`.",{"type":14,"level":15,"text":16,"id":17},"heading",2,"Requirements","requirements",{"type":19,"items":20},"list",[21,22],".NET 6 or newer.","A [secret key](\u002Fdocs\u002Fapi-keys), and a webhook signing secret if you receive [webhooks](\u002Fdocs\u002Fwebhooks).",{"type":14,"level":15,"text":24,"id":25},"Install","install",{"type":27,"samples":28},"code",[29,33],{"label":30,"lang":31,"code":32},".NET CLI","bash","dotnet add package Fingerly",{"label":34,"lang":35,"code":36},"PackageReference","xml","\u003CPackageReference Include=\"Fingerly\" Version=\"0.1.0\" \u002F>",{"type":14,"level":15,"text":38,"id":39},"Read an event","read-an-event",{"type":11,"text":41},"Create one client with your secret key and reuse it. The key decides the regional API and the environment the client reads.",{"type":27,"samples":43},[44],{"label":6,"lang":45,"code":46},"csharp","using Fingerly;\n\nvar fingerly = new FingerlyClient(Environment.GetEnvironmentVariable(\"FINGERLY_SECRET_KEY\"));\nvar ev = await fingerly.Events.GetAsync(\"01a0a84b-e6a2-7c09-9f51-0b3d7a26c8e4\");",{"type":11,"text":48},"An event has the fields listed in [Get an event](\u002Freference\u002Fget-event#response). `suspect_score` is `null` when the request was not scored.",{"type":14,"level":15,"text":50,"id":51},"Verify a checkout","verify-a-checkout",{"type":11,"text":53},"Read the event your client identified, check it belongs to this action and is recent, then decide on its level. See [server-side verification](\u002Fdocs\u002Fserver-side-verification).",{"type":27,"samples":55},[56],{"label":6,"lang":45,"code":57},"public async Task\u003Cstring> DecideAsync(string orderId, string requestId, CancellationToken ct)\n{\n    Event ev;\n    try\n    {\n        ev = await _fingerly.Events.GetAsync(requestId, ct);\n    }\n    catch (FingerlyApiException e) when (e.Status == 404)\n    {\n        return \"refuse\";\n    }\n\n    if (ev.Tag != $\"checkout:{orderId}\") return \"refuse\";\n    if (DateTimeOffset.UtcNow - ev.OccurredAt > TimeSpan.FromMinutes(2)) return \"refuse\";\n\n    return ev.SuspectLevel switch\n    {\n        \"high\" => \"review\",\n        \"medium\" => \"challenge\",\n        _ => \"allow\",\n    };\n}",{"type":14,"level":15,"text":59,"id":60},"Verify a webhook","verify-a-webhook",{"type":11,"text":62},"Check the signature over the raw request body before parsing it. The helper rejects timestamps more than five minutes from now.",{"type":27,"samples":64},[65],{"label":6,"lang":45,"code":66},"app.MapPost(\"\u002Fwebhooks\u002Ffingerly\", async (HttpRequest request, IEventQueue queue) =>\n{\n    using var reader = new StreamReader(request.Body);\n    var body = await reader.ReadToEndAsync();\n\n    var valid = FingerlyWebhook.Verify(\n        secret: builder.Configuration[\"Fingerly:WebhookSecret\"]!,\n        payload: body,\n        timestamp: request.Headers[\"x-fingerly-timestamp\"],\n        signature: request.Headers[\"x-fingerly-signature\"]);\n    if (!valid) return Results.BadRequest();\n\n    var ev = FingerlyWebhook.Parse(body);\n    await queue.EnqueueAsync(ev.Id, body);   \u002F\u002F deduplicate on the event ID\n    return Results.NoContent();\n});",{"type":14,"level":15,"text":68,"id":69},"Dependency injection","dependency-injection",{"type":27,"samples":71},[72],{"label":73,"lang":45,"code":74},"Program.cs","builder.Services.AddFingerly(options =>\n{\n    options.SecretKey = builder.Configuration[\"Fingerly:SecretKey\"];\n});\n\n\u002F\u002F Inject FingerlyClient wherever it is needed. It is registered as a singleton.",{"type":14,"level":15,"text":76,"id":77},"API","api",{"type":79,"columns":80,"rows":84},"table",[81,82,83],"Member","Returns","Notes",[85,89,93,97],[86,87,88],"`new FingerlyClient(secretKey)`","`FingerlyClient`","Or `new FingerlyClient(new FingerlyClientOptions { ... })` for `Endpoint` and `HttpClient`.",[90,91,92],"`Events.GetAsync(requestId, ct)`","`Task\u003CEvent>`","Throws `FingerlyApiException` with `Status` for a non-2xx response.",[94,95,96],"`Events.ListAsync(EventListOptions, ct)`","`Task\u003CEventPage>`","Options: `From`, `To` (`DateTimeOffset`), `Page`, `Limit`, `Visitor`, `Level`.",[98,99,100],"`FingerlyWebhook.Verify(secret, payload, timestamp, signature)`","`bool`","Five minutes of tolerance. `FingerlyWebhook.Parse` reads the envelope.",{"type":11,"text":102},"Event properties are PascalCase: `ev.RequestId`, `ev.SuspectLevel`, `ev.OccurredAt`.",[104,105,106,107,108,109,110],{"id":17,"text":16,"level":15},{"id":25,"text":24,"level":15},{"id":39,"text":38,"level":15},{"id":51,"text":50,"level":15},{"id":60,"text":59,"level":15},{"id":69,"text":68,"level":15},{"id":77,"text":76,"level":15},"2026-09-17T08:28:36.000Z",1789667797825]