> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shieldlabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# C#/.NET

> Read and evaluate a stored identification, then verify raw-body webhook signatures in your backend.

Read and evaluate a stored identification, then verify raw-body webhook signatures in your backend. This guide uses the supported ShieldLabs package API, not a standalone generated client.

## Before you start

The example uses the .NET 8 SDK and ASP.NET Core. The library also targets .NET Standard 2.0.

Register and verify the website domain in your [analytics dashboard](https://app.shieldlabs.ai/). Use credentials from the same domain and environment as the browser check. For a fresh account, start with [Quick Start](/quickstart).

## Get your credentials

Copy the domain's **Private API Key** from **Integration > API keys** and store it as `SHIELDLABS_API_KEY` in your server environment. For webhook verification, also set `SHIELDLABS_WEBHOOK_SECRET` from **Integration > Webhooks**. Never expose either secret to the browser.

## Install the SDK

```bash theme={null}
dotnet add package ShieldLabs
```

## Add the integration

Both server-side halves in one ASP.NET Core app. Create it with `dotnet new web`, run
`dotnet add package ShieldLabs`, replace `Program.cs` with the code below, set
`SHIELDLABS_API_KEY` (Private API Key, `sec_...`) and `SHIELDLABS_WEBHOOK_SECRET` (endpoint signing
secret, `whsec_...`), then `dotnet run`.

```csharp theme={null}
using System.Collections.Concurrent;
using ShieldLabs;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// One client per domain, shared by every request: it is safe for concurrent use.
var shieldlabs = new ShieldLabsClient(new ShieldLabsClientOptions
{
    ApiKey = Environment.GetEnvironmentVariable("SHIELDLABS_API_KEY"), // sec_your_private_key
});
var webhookSecret = Environment.GetEnvironmentVariable("SHIELDLABS_WEBHOOK_SECRET") ?? ""; // whsec_your_signing_secret

// One identification authorizes one action. This in-memory store keeps the example short; in
// production, claim request IDs atomically in a shared store (Redis SET NX, a unique key).
var usedRequestIds = new ConcurrentDictionary<string, DateTimeOffset>();

// 1. The page posts the requestId it received from the browser agent with the signup form.
app.MapPost("/signup", async (SignupForm form, CancellationToken cancellationToken) =>
{
    Identification? identification;
    try
    {
        // Scoring is asynchronous: this polls until the verdict is stored (10-second budget by default).
        identification = await shieldlabs.Identifications.GetAsync(form.RequestId ?? "", cancellationToken: cancellationToken);
    }
    catch (ValidationException)
    {
        return Results.BadRequest(new { error = "requestId must be a UUID" });
    }
    catch (ShieldLabsException)
    {
        // Network, key or rate-limit problem: the action stays unverified.
        return Results.Json(new { error = "unverified" }, statusCode: 503);
    }

    // 2. Missing, reused, stale, rate-limited, automated or dangerous: refuse.
    var firstUse = identification is not null && usedRequestIds.TryAdd(identification.RequestId, DateTimeOffset.UtcNow);
    var evaluation = Risk.Evaluate(identification, new EvaluateOptions { IsReplay = _ => !firstUse });
    if (!evaluation.Ok)
    {
        return Results.Json(new { error = "refused", reason = evaluation.Reason }, statusCode: 403);
    }

    // Create the account here.
    return Results.Ok(new { ok = true, band = evaluation.Band });
});

// 3. Verify each webhook delivery over the raw body before reading it.
app.MapPost("/webhooks/shieldlabs", async (HttpRequest request) =>
{
    using var body = new MemoryStream();
    await request.Body.CopyToAsync(body);

    WebhookEvent evt;
    try
    {
        evt = WebhookEvents.ConstructEvent(body.ToArray(), request.Headers[WebhookSignature.HeaderName], webhookSecret);
    }
    catch (SignatureVerificationException)
    {
        return Results.Unauthorized();
    }
    catch (WebhookParseException)
    {
        return Results.BadRequest();
    }

    if (evt is IdentificationScoredEvent scored)
    {
        // Handle each request ID once: future retries resend identical bytes.
        app.Logger.LogInformation("{RequestId}: risk score {RiskScore}", scored.Data.RequestId, scored.Data.RiskScore);
    }

    return Results.Ok();
});

app.Run();

record SignupForm(string? RequestId, string? Email);
```

`POST /signup` with `{"requestId":"..."}` answers `200` with the band, `403` with a `reason`
(`missing`, `replayed`, `stale`, `rate_limited`, `no_device_signals`, `blocked_flag`,
`blocked_band`), `400` for a malformed request ID and `503` when the verdict could not be read. A
fuller app with the same routes lives in [`examples/MinimalApi`](https://github.com/ShieldLabs-ai/shieldlabs-dotnet/tree/main/examples/MinimalApi).

## Test the complete flow

1. Use the Request ID from a real browser check on the same domain as the Private API Key.
2. Confirm the server retrieves that identification and its Risk Score.
3. Test an invalid or missing ID and an unavailable API: none should be treated as a clean identification.
4. For webhooks, test the original raw body with its signature, then change one byte and confirm rejection.

In-memory replay stores in examples are demonstrations, not shared production storage. Claim accepted IDs atomically in a durable database or cache, enforce freshness, authenticate the action, and check the expected domain and user association. Reading a valid identification alone does not authorize a business action.

## Troubleshooting

* No History row: confirm the registered domain, credential/environment match and that the browser remained open while collectors posted.
* Missing or pending verdict: scoring is asynchronous. The server helper waits within a bounded budget; handle a missing result and API errors explicitly.
* Authentication error: use the Private API Key for History, not a Public Key, Management Secret Key or MCP OAuth token.
* Invalid webhook signature: verify the original raw bytes with the endpoint's full signing secret, before trusting parsed JSON.

## Next steps

* [Runnable example](https://github.com/ShieldLabs-ai/shieldlabs-dotnet/tree/803a2f93b8feda725c6b077689592d5911cf9c37/examples/MinimalApi)
* [SDK reference and changelog](https://github.com/ShieldLabs-ai/shieldlabs-dotnet)
* [Identification flow](/api/identification-flow)
* [Server API](/api/server-api)
* [Webhook setup](/setup/webhooks)
* [Content Security Policy](/setup/csp)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.