Skip to content

How to manage credential status with Status Lists

Field Value
Package maturity Spec-tracking (Token Status List draft-20)
Code status Mixed -- runnable package APIs with illustrative service wiring
Related concept Verifiable Credentials
Related tutorial Tutorials
Audience Developers implementing credential lifecycle management on issuer and verifier sides.
Purpose Walk through end-to-end status management using Status Lists - creating lists, issuing credentials with status entries, revoking credentials, and wiring status-list checking into the verifier pipeline - using SdJwt.Net.StatusList.
Scope Status list service setup, credential-to-index binding, revocation operations, CDN publishing, and verifier-side status checking. Out of scope: token introspection (see Token Introspection), hybrid checking strategies.
Success criteria Reader can issue a credential bound to a status list index, revoke it, publish the updated bitstring, and wire status-list checking into their verifier pipeline so that revoked credentials are rejected.

What your application still owns

This guide does not provide: production key custody, CDN deployment, cache invalidation strategy, fail-open/fail-closed policy decisions, monitoring and alerting on status list freshness, or incident response for missed revocations.

Freshness note: Status lists are cached artifacts. Token expiry, CDN TTL, and verifier cache TTL all influence how quickly a revocation propagates. Decide on a fail-open vs. fail-closed policy when a fresh status list is temporarily unavailable.

This guide uses architectural pseudocode for service wiring. For concrete package usage, see samples/SdJwt.Net.Samples/Standards/VerifiableCredentials/StatusListExample.cs.


Key decisions

Decision Options Guidance
Status list key separate from credential key? Yes/No Always yes for production
Publishing frequency? On-demand or scheduled On-demand for immediate revocation needs
Cache TTL for verifiers? Seconds to minutes Balance between freshness and load
Hosting strategy? CDN, API, or hybrid CDN for high-volume verification
Fail-closed on status unavailability? Yes/No Yes for high-risk flows

Prerequisites

Ensure your project references the necessary NuGet packages:

dotnet add package SdJwt.Net.StatusList
dotnet add package SdJwt.Net.Oid4Vci

1. Configure the status list service (issuer side)

The Status List Service handles generating and hosting the compressed bitstrings. Host these files statically on a CDN for maximum performance and privacy — verifiers should not need to query your API for every user login, as that would expose user activity patterns.

In your Program.cs:

using SdJwt.Net.StatusList.Issuer;

var builder = WebApplication.CreateBuilder(args);

// Register your status-list infrastructure and app services here.
// Example package primitive used by issuer code:
var statusManager = new StatusListManager(signingKey, SecurityAlgorithms.EcdsaSha256);

var app = builder.Build();

2. Issue a credential with status (issuer side)

When issuing a credential, include a status claim that references the status list URI and the credential's index. The verifier will later check this index to determine revocation status.

app.MapPost("/issue-employee-id", (CredentialRequest request) =>
{
    // 1. Assign a unique index for this credential in the status list
    var statusIndex = AllocateNextIndex(request.UserId);

    // 2. Build the SD-JWT VC payload with status reference
    var vcPayload = new SdJwtVcPayload
    {
        Issuer = "https://issuer.example.com",
        Subject = $"did:example:employee:{request.UserId}",
        IssuedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
        ExpiresAt = DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeSeconds(),
        Vct = "EmployeeIdCredential",

        // Status claim references the status list URI and index
        Status = new
        {
            status_list = new
            {
                idx = statusIndex,
                uri = "https://issuer.example.com/.well-known/status-list"
            }
        },

        AdditionalData = new Dictionary<string, object>
        {
            ["name"] = request.Name,
            ["department"] = request.Department
        }
    };

    var result = vcIssuer.Issue("EmployeeIdCredential", vcPayload, options);

    return Results.Ok(new { credential = result.Issuance });
});

The VC now contains a status_list object indicating its URL and index.

3. Revoke a credential (issuer side)

When an employee leaves the company, revoke their credential.

app.MapPost("/revoke-employee", async (string userId) =>
{
    // Look up the credential's index in the status list
    var credentialIndex = GetCredentialIndex(userId);

    // Revoke the credential by updating the status list token
    var updatedToken = await statusManager.RevokeTokensAsync(
        existingStatusListToken,
        new[] { credentialIndex });

    // Publish the updated token to the CDN / .well-known endpoint
    await PublishStatusListToken(updatedToken);

    return Results.Ok();
});

The StatusListManager handles compression and encoding natively. The RevokeTokensAsync() method sets the status bits for the given indices to StatusType.Invalid and returns a new signed status list token.

4. Check credential status (verifier side)

When a verifier receives an SD-JWT presentation, it must check whether the credential has been revoked.

Because SdJwt.Net.Oid4Vp and SdJwt.Net.HAIP integrate with the Status List package, this check runs automatically during verification when the StatusListService is registered in the verifier's Dependency Injection container.

// In the Verifier application, use StatusListVerifier to check credential status.
using SdJwt.Net.StatusList.Verifier;
using SdJwt.Net.StatusList.Models;

var statusVerifier = new StatusListVerifier(httpClient);

When verifying a credential, extract its status claim and check against the published status list:

// Parse the status claim from the presented credential
var statusClaim = new StatusClaim
{
    StatusList = new StatusListReference
    {
        Index = 42,
        Uri = "https://issuer.example.com/.well-known/status-list"
    }
};

var statusResult = await statusVerifier.CheckStatusAsync(
    statusClaim,
    issuerKeyProvider: async iss => await FetchIssuerKey(iss));

if (!statusResult.IsValid)
{
    // Credential has been revoked or suspended
    return Results.Unauthorized();
}