Verifiabl 0.4.0
Prefix ReservedRenamed to Verifiabl.Issuer
dotnet add package Verifiabl --version 0.4.0
NuGet\Install-Package Verifiabl -Version 0.4.0
<PackageReference Include="Verifiabl" Version="0.4.0" />
<PackageVersion Include="Verifiabl" Version="0.4.0" />
<PackageReference Include="Verifiabl" />
paket add Verifiabl --version 0.4.0
#r "nuget: Verifiabl, 0.4.0"
#:package Verifiabl@0.4.0
#addin nuget:?package=Verifiabl&version=0.4.0
#tool nuget:?package=Verifiabl&version=0.4.0
Verifiabl .NET SDK
Official .NET SDK for issuing Verifiabl payslip QR codes.
Add a scannable QR code to each payslip you issue. You register the non-PII payslip data with Verifiabl and encrypt the employee's personal details on your own infrastructure, so they live only inside the QR code on the document and never reach Verifiabl.
Verifiabl is for accredited payroll providers. You receive sandbox credentials at onboarding. Full documentation is at docs.verifiabl.io.
Installation
dotnet add package Verifiabl
In an app that uses dependency injection, add the integration package too:
dotnet add package Verifiabl.Extensions.DependencyInjection
Supported targets: .NET 8+, and .NET Framework 4.7.2+ (Windows). On .NET Framework, AES-GCM is provided by the bundled Microsoft.Bcl.Cryptography dependency.
Two namespaces: offline and networked
The split is deliberate, so you can see at a glance which half of the SDK touches the network.
| Namespace | Contents | Network |
|---|---|---|
Verifiabl |
Pii, PiiFields, VerifiablCrypto, EncryptedPii, EncryptionMetadata, VerifiablBarcode, BarcodeParts, BarcodeSvgOptions, VerifiablReference, VerifiablEnvironment, VerifiablEndpoints |
None. Pure functions you can call from anywhere, including a hot PDF-rendering loop. |
Verifiabl.Client |
IVerifiablClient, VerifiablClient, VerifiablClientOptions, VerifiablAuth, the request/response types, VerifiablApiException and friends |
Calls the Verifiabl issuer API. |
Encryption, PII formatting, reference generation, and barcode rendering all happen on your infrastructure with no network call — the Verifiabl namespace has no client in it to make one.
Registering the client
With dependency injection, using Verifiabl.Extensions.DependencyInjection:
using Microsoft.Extensions.DependencyInjection;
using Verifiabl;
using Verifiabl.Client;
builder.Services.AddVerifiablClient(options =>
{
options.Environment = VerifiablEnvironment.Sandbox;
options.Auth = VerifiablAuth.ClientCredentials(
builder.Configuration["Verifiabl:ClientId"]!,
builder.Configuration["Verifiabl:ClientSecret"]!);
});
IVerifiablClient is registered as a singleton — it caches OAuth access tokens, so a shorter lifetime would fetch a new token on every resolve — and its HttpClient comes from IHttpClientFactory. Inject IVerifiablClient wherever you need it; substitute your own implementation in tests.
Without dependency injection, construct it once and reuse it (it is thread-safe):
var client = new VerifiablClient(new VerifiablClientOptions
{
Environment = VerifiablEnvironment.Sandbox,
Auth = VerifiablAuth.ClientCredentials(clientId, clientSecret),
});
Getting started
This is the self-managed flow: register the payslip, encrypt the personal details locally, and generate the QR code yourself. You need three values from onboarding: your OAuth client ID and secret, and your encryption key.
using Verifiabl;
using Verifiabl.Client;
// Your 32-byte key, from onboarding. Load it from a secrets manager.
byte[] key = Convert.FromBase64String(
Environment.GetEnvironmentVariable("VERIFIABL_ENCRYPTION_KEY_BASE64")!);
// 1. Format and encrypt the employee's details locally.
string pii = Pii.Format(new PiiFields
{
EmployeeName = "Jane A. Doe",
Position = "Senior Developer",
Department = "Engineering",
EmployerAbn = "12345678901",
Bsb = "062-000",
AccountNumber = "12345678",
AccountName = "Jane A Doe",
});
EncryptedPii encrypted = VerifiablCrypto.EncryptPii(pii, key);
// 2. Register the non-PII data. Verifiabl returns a Verifiabl reference.
RegisterNonPiiResponse registration = await client.RegisterNonPiiAsync(new RegisterNonPiiRequest
{
Schema = "au.payslip.v1",
IssuedAt = DateTimeOffset.UtcNow,
PayslipNonPii = new PayslipNonPii
{
PeriodStart = "2026-05-01",
PeriodEnd = "2026-05-31",
// au.payslip.v1 requires these; keys and value types are set by the schema.
AdditionalData = new Dictionary<string, object?>
{
["payment_date"] = "2026-06-04",
["currency"] = "AUD",
["gross_cents"] = 812_500,
["paygw_cents"] = 203_000,
["net_cents"] = 609_500,
["ytd_gross_cents"] = 8_937_500,
["ytd_paygw_cents"] = 2_233_000,
},
},
EncryptionMetadata = encrypted.Metadata,
});
// 3. Render the QR code and embed the SVG in your payslip PDF.
BarcodeSvgResult badge = VerifiablBarcode.CreateSvg(
new BarcodeParts(registration.VerifiablReference, encrypted.Ciphertext),
new BarcodeSvgOptions { Environment = VerifiablEnvironment.Sandbox });
The compiler enforces the mandatory fields: Schema, IssuedAt, PayslipNonPii, and EncryptionMetadata are required, so an incomplete request will not build.
AdditionalData is passed to the API verbatim under the exact keys you supply. Values may be strings, booleans, numbers, null, nested dictionaries, or sequences of those; anything else throws an ArgumentException naming the key. Which keys your schema requires is documented per schema — the au.payslip.v1 set is shown above.
VerifiablBarcode.CreateSvg produces a standalone SVG that scales to any size without losing quality; embed it directly in your PDF pipeline when it supports vector images. If it needs a raster image, use VerifiablBarcode.CreatePng: it composites the badge deterministically with no native dependencies, so the same record produces the byte-identical raster in every Verifiabl SDK, and QR module edges stay crisp (rasterising the SVG with a general renderer blurs them and costs scannability). PNG output comes in fixed pixel widths (480, 720, 960 or 1440; the physical print size is set where you place the image in the PDF). See the docs for both flows.
Retries and idempotency
Failed requests are retried automatically with exponential backoff (VerifiablClientOptions.MaxRetries, default 2). The Verifiabl reference is the idempotency key, so retries are only applied where they are safe. RegisterNonPiiAsync generates a reference client-side (or uses the one you set on the request), so the API deduplicates a re-send and the SDK retries it on throttling, timeouts, 5xx, and network faults — same as batch registration. RegisterAndBuildBarcodeAsync lets the API assign the reference and cannot be deduplicated, so it retries only 429, which is enforced before any processing.
Batch registration
For pay runs, register up to 1000 records in one request with RegisterNonPiiBatchAsync. The provider generates each Verifiabl reference up-front with VerifiablReference.Generate() and includes it on each record, so the whole batch can go in one round trip. Results are returned index-aligned to the input; one bad record never fails the whole batch.
DateTimeOffset issuedAt = DateTimeOffset.UtcNow;
var prepared = payslips.Select(payslip =>
{
string verifiablReference = VerifiablReference.Generate();
EncryptedPii encrypted = VerifiablCrypto.EncryptPii(Pii.Format(payslip.Pii), key);
// Keep the ciphertext alongside the reference locally: you need both to render the barcode.
return (verifiablReference, encrypted, payslip);
}).ToList();
RegisterNonPiiBatchResponse batch = await client.RegisterNonPiiBatchAsync(
prepared.Select(item => new BatchRecord
{
VerifiablReference = item.verifiablReference,
Schema = "au.payslip.v1",
IssuedAt = issuedAt,
PayslipNonPii = new PayslipNonPii
{
PeriodStart = item.payslip.PeriodStart,
PeriodEnd = item.payslip.PeriodEnd,
AdditionalData = item.payslip.SchemaFields,
},
EncryptionMetadata = item.encrypted.Metadata,
}));
foreach (BatchRecordResult result in batch.Results)
{
if (result.Status == BatchRecordStatuses.Error)
{
logger.LogError(
"Record {Reference} failed: {Code} {Detail}",
result.VerifiablReference, result.Code, result.Detail);
}
}
Environments
Set Environment to VerifiablEnvironment.Production (the default) or VerifiablEnvironment.Sandbox. Pass the same value to the client and the barcode renderer, so the scan URL printed on the document matches where the record was registered.
Errors
Every failure an API call reports derives from VerifiablException, so one catch clause covers the client. Match the specific types when you want to react differently.
try
{
await client.RegisterNonPiiAsync(request);
}
catch (VerifiablApiException exception) when (exception.Code == VerifiablErrorCodes.ValidationFailed)
{
logger.LogWarning("Validation failed, request id {RequestId}", exception.RequestId);
}
catch (VerifiablException exception)
{
// VerifiablApiException, VerifiablAuthException, VerifiablTimeoutException,
// and VerifiablTransportException all land here.
logger.LogError(exception, "Verifiabl registration failed");
}
| Exception | Raised when |
|---|---|
VerifiablApiException |
The API returned a non-2xx response. Carries Status, a stable Code, the parsed Body, and a RequestId to quote to support. |
VerifiablIvReuseException |
A registration was rejected because the record's encryption IV is already registered to your issuer. Derives from VerifiablApiException, so the catch clause above still covers it. |
VerifiablAuthException |
An OAuth access token could not be obtained. |
VerifiablTimeoutException |
The call exceeded VerifiablClientOptions.Timeout, which covers the token fetch, the request, and every retry. |
VerifiablTransportException |
A network fault prevented a response (the HttpRequestException is the InnerException), or a 2xx response was not usable JSON. |
Two things are deliberately not VerifiablException: an ArgumentException for an incomplete or malformed request, thrown before anything is sent, and an OperationCanceledException when you cancel the CancellationToken you passed in.
Reused encryption IV
Registration rejects an IV that your issuer has already used. VerifiablCrypto.EncryptPii draws a fresh IV on every call, so this occurs when stored EncryptionMetadata is sent again with different content.
The SDK does not re-encrypt and retry for you. Encrypt the payslip again, resend the record with the new encryption metadata, and rebuild any barcode that you rendered from the previous ciphertext. Resending the record unchanged gives the same result.
Single registrations throw VerifiablIvReuseException, whose Code is VerifiablErrorCodes.IvReused.
try
{
await client.RegisterNonPiiAsync(request);
}
catch (VerifiablIvReuseException)
{
// Encrypt again for a fresh IV and ciphertext, then register and render again.
EncryptedPii encrypted = VerifiablCrypto.EncryptPii(pii, key);
}
Batch records come back as an error result, which BatchRecordResult.IsIvReused matches. It covers both cases the API reports: a collision with a stored record, and a repeat within the same batch (where the first record still registers).
RegisterNonPiiBatchResponse batch = await client.RegisterNonPiiBatchAsync(records);
List<string> toReEncrypt = batch.Results
.Where(result => result.IsIvReused)
.Select(result => result.VerifiablReference)
.ToList();
Security
Employee PII is encrypted on your infrastructure and never reaches Verifiabl. Keep your encryption key and OAuth secret in a secrets manager. See the security model for the full detail.
Documentation
Full API reference, the alternative API flow, barcode placement rules, and the security model are at docs.verifiabl.io.
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 is compatible. 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 Framework | net472 is compatible. net48 was computed. net481 was computed. |
-
.NETFramework 4.7.2
- Microsoft.Bcl.Cryptography (>= 10.0.5)
- Net.Codecrete.QrCodeGenerator (>= 3.0.0)
- System.Text.Json (>= 10.0.5)
-
net10.0
- Net.Codecrete.QrCodeGenerator (>= 3.0.0)
-
net8.0
- Net.Codecrete.QrCodeGenerator (>= 3.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Verifiabl:
| Package | Downloads |
|---|---|
|
Verifiabl.Extensions.DependencyInjection
Microsoft.Extensions.DependencyInjection integration for the Verifiabl .NET SDK: registers IVerifiablClient as a singleton wired to IHttpClientFactory. |
GitHub repositories
This package is not used by any popular GitHub repositories.