Catalisa.Biometrics
0.1.0
dotnet add package Catalisa.Biometrics --version 0.1.0
NuGet\Install-Package Catalisa.Biometrics -Version 0.1.0
<PackageReference Include="Catalisa.Biometrics" Version="0.1.0" />
<PackageVersion Include="Catalisa.Biometrics" Version="0.1.0" />
<PackageReference Include="Catalisa.Biometrics" />
paket add Catalisa.Biometrics --version 0.1.0
#r "nuget: Catalisa.Biometrics, 0.1.0"
#:package Catalisa.Biometrics@0.1.0
#addin nuget:?package=Catalisa.Biometrics&version=0.1.0
#tool nuget:?package=Catalisa.Biometrics&version=0.1.0
Catalisa.Biometrics
.NET SDK for Catalisa Biometrics — liveness and face-match sessions, signed evidence and webhook verification.
- .NET 8 and .NET Standard 2.0, which also covers .NET Framework 4.6.1+
- One dependency, BouncyCastle: .NET has no Ed25519, and the older targets cannot read a PEM — so all the crypto goes through it
- Typed exception carrying the API's own error code
dotnet add package Catalisa.Biometrics
How the flow works
- Your server creates a session and receives
Handoff.CaptureUrl. - The person opens that URL (browser tab, iframe or WebView) and does the challenge.
- The decision arrives on your server through the
biometrics.session.completedwebhook, or throughGetSessionAsync.
The capture page never reveals the result to the person in front of the camera — on purpose, so it cannot be used as a fraud oracle. Never decide anything from front-end events.
Quick start
using Catalisa.Biometrics;
using var client = new BiometricsClient(Environment.GetEnvironmentVariable("CATALISA_API_KEY")!);
var session = await client.CreateSessionAsync(new CreateSessionInput
{
Flow = Flows.LivenessOnly,
Purpose = "abertura de conta",
Metadata = new Dictionary<string, string> { ["orderId"] = "123" },
});
// send the person to session.Handoff!.CaptureUrl
var envelope = await client.GetSessionAsync(session.SessionId);
if (envelope.Status == SessionStatuses.Approved)
{
// proceed
}
API
| Member | What it does |
|---|---|
CreateSessionAsync |
Opens a session; the envelope comes with Handoff |
GetSessionAsync |
Reads the session, with decision, checks and evidence |
ListSessionsAsync |
Lists sessions, newest first, with filters and pagination |
CancelSessionAsync |
Closes a session that has not settled; the allowance slot comes back |
GetEvidenceAsync |
Signed evidence plus short-lived download links |
GetEvidenceKeysAsync |
Published signing keys, retired ones included |
Evidence.Verify |
Checks the signature offline, without calling Catalisa |
SubmitCaptureAsync |
Uploads a capture recorded by your own app |
WebhookVerifier.Verify |
Verifies a delivery and returns the parsed event |
Errors
Every failure throws BiometricsException with the API's own code:
try
{
var session = await client.CreateSessionAsync(input);
}
catch (BiometricsException e) when (e.IsQuotaExceeded)
{
// the customer's monthly allowance is gone (402); e.Details has the numbers
}
catch (BiometricsException e) when (e.IsSubaccountSuspended)
{
// suspended: nothing opens until it is reactivated
}
catch (BiometricsException e) when (e.IsTransient)
{
// rate limit, server fault or network: worth trying again
}
Retries
- 429 is retried for every method, honoring
Retry-After: the rate limiter refuses before anything runs. - 5xx and network failures are retried only for reads.
- Session creation is never retried on those: the server does not deduplicate by
Idempotency-Keyyet, and a retry could open two sessions (and spend the allowance twice).
Webhooks
The delivery is signed with the subscription's private key; you verify with the public one. Verify the raw bytes — model binding that re-serializes the JSON changes them and the signature will not match.
var verifier = new WebhookVerifier(publicKeysByKeyId);
app.MapPost("/webhooks/biometrics", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var raw = await reader.ReadToEndAsync();
try
{
var evt = verifier.Verify(name => request.Headers[name].FirstOrDefault(), raw);
// evt.Id is stable: use it for idempotency, a delivery can repeat
return Results.Ok(); // answer fast; do the work afterwards
}
catch (WebhookVerificationException)
{
return Results.BadRequest();
}
});
The engine does not refuse old deliveries — the receiver does, with a five-minute tolerance by default.
Signed evidence
var keys = await client.GetEvidenceKeysAsync();
var evidence = await client.GetEvidenceAsync(sessionId);
var result = Evidence.Verify(sessionId, envelope.Attempt, evidence.BundleHash, evidence.Signature!, keys);
// result.Valid says whether this is the bundle Catalisa signed
The message is sessionId|attempt|bundleHash|signedAt, signed with Ed25519. Retired keys stay published, so evidence signed years ago still verifies.
Test environment
An API key belongs to one world and says so on every session it opens:
test (sandbox) |
live |
|
|---|---|---|
| Engine | Simulated, deterministic | The real one configured for the account |
| Result | Decided by the CPF ending in SubjectRef |
Decided by the engine |
| Allowance | Not spent | Spent |
| Billing | Never billed | Billed |
| Visibility | A test key only reads test sessions; a live key only reads live ones |
A test key needs no engine configured, so you can integrate on day one. Session.IsSandbox tells a rehearsal from the real thing.
Endings that drive the sandbox result: …-25 (or any other) approves, …-11 comes back inconclusive for human review, …-55 asks for a second attempt and then approves, …-66 fails as an engine error, …-00, …-33 and …-44 reject by face match, liveness and continuity.
Subaccounts
With an organization key, set BiometricsOptions.SubaccountId to act on behalf of a subaccount; with a subaccount key the scope is already bound. Envelopes and webhook payloads carry subaccountId (null for organization sessions).
Custom capture
SubmitCaptureAsync uploads a video your own app recorded, authenticating with the session's CaptureToken — the account key never leaves your server. Only video/webm and video/mp4 are accepted, up to 16 MiB and 24 seconds.
Prefer the hosted page when you can: it already runs the quality checks that keep a useless recording from spending an attempt.
Development
dotnet test tests/Catalisa.Biometrics.Tests.csproj
The tests share vectors/vectors.json with the Node, Python, Go and PHP SDKs — the same signatures, produced by the building block's own code.
Releases go out through GitHub Actions with NuGet trusted publishing: no API key is stored anywhere. Tag packages/dotnet/vX.Y.Z and the workflow tests, packs and pushes.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- BouncyCastle.Cryptography (>= 2.4.0)
- System.Net.Http.Json (>= 8.0.1)
- System.Text.Json (>= 8.0.5)
-
net8.0
- BouncyCastle.Cryptography (>= 2.4.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 121 | 9/20/2026 |