The Fingerly NuGet package reads stored events by request ID and verifies webhook signatures. Every call is asynchronous and accepts a CancellationToken.
Requirements
- .NET 6 or newer.
- A secret key, and a webhook signing secret if you receive webhooks.
Install
dotnet add package Fingerly
Read an event
Create one client with your secret key and reuse it. The key decides the regional API and the environment the client reads.
using Fingerly;
var fingerly = new FingerlyClient(Environment.GetEnvironmentVariable("FINGERLY_SECRET_KEY"));
var ev = await fingerly.Events.GetAsync("01a0a84b-e6a2-7c09-9f51-0b3d7a26c8e4");
An event has the fields listed in Get an event. suspect_score is null when the request was not scored.
Verify a checkout
Read the event your client identified, check it belongs to this action and is recent, then decide on its level. See server-side verification.
public async Task<string> DecideAsync(string orderId, string requestId, CancellationToken ct)
{
Event ev;
try
{
ev = await _fingerly.Events.GetAsync(requestId, ct);
}
catch (FingerlyApiException e) when (e.Status == 404)
{
return "refuse";
}
if (ev.Tag != $"checkout:{orderId}") return "refuse";
if (DateTimeOffset.UtcNow - ev.OccurredAt > TimeSpan.FromMinutes(2)) return "refuse";
return ev.SuspectLevel switch
{
"high" => "review",
"medium" => "challenge",
_ => "allow",
};
}
Verify a webhook
Check the signature over the raw request body before parsing it. The helper rejects timestamps more than five minutes from now.
app.MapPost("/webhooks/fingerly", async (HttpRequest request, IEventQueue queue) =>
{
using var reader = new StreamReader(request.Body);
var body = await reader.ReadToEndAsync();
var valid = FingerlyWebhook.Verify(
secret: builder.Configuration["Fingerly:WebhookSecret"]!,
payload: body,
timestamp: request.Headers["x-fingerly-timestamp"],
signature: request.Headers["x-fingerly-signature"]);
if (!valid) return Results.BadRequest();
var ev = FingerlyWebhook.Parse(body);
await queue.EnqueueAsync(ev.Id, body); // deduplicate on the event ID
return Results.NoContent();
});
Dependency injection
builder.Services.AddFingerly(options =>
{
options.SecretKey = builder.Configuration["Fingerly:SecretKey"];
});
// Inject FingerlyClient wherever it is needed. It is registered as a singleton.
API
| Member | Returns | Notes |
|---|---|---|
new FingerlyClient(secretKey) | FingerlyClient | Or new FingerlyClient(new FingerlyClientOptions { ... }) for Endpoint and HttpClient. |
Events.GetAsync(requestId, ct) | Task<Event> | Throws FingerlyApiException with Status for a non-2xx response. |
Events.ListAsync(EventListOptions, ct) | Task<EventPage> | Options: From, To (DateTimeOffset), Page, Limit, Visitor, Level. |
FingerlyWebhook.Verify(secret, payload, timestamp, signature) | bool | Five minutes of tolerance. FingerlyWebhook.Parse reads the envelope. |
Event properties are PascalCase: ev.RequestId, ev.SuspectLevel, ev.OccurredAt.