How to Revoke Credentials
Purpose
This guide shows you how to revoke Verifiable Credentials using TrustWeave’s revocation system. You’ll learn how to create status lists, revoke credentials, check revocation status, and handle revocation lifecycle management.
What you’ll accomplish:
- Configure revocation support in TrustWeave
- Create status lists for tracking revoked credentials
- Revoke credentials when needed
- Check if credentials are revoked
- Understand revocation status verification
Why this matters: Revocation is essential for credential lifecycle management. It allows issuers to invalidate credentials that are no longer valid (e.g., expired licenses, terminated employment, compromised credentials) while maintaining cryptographic proof of the revocation.
Prerequisites
- Kotlin: 2.2.21+ or higher
- Java: 21 or higher
- TrustWeave SDK: Latest version
- Dependencies:
distribution-allwith revocation support
Required imports:
1
2
3
4
5
import org.trustweave.trust.TrustWeave
import org.trustweave.credential.model.vc.VerifiableCredential
import org.trustweave.credential.revocation.StatusPurpose
import org.trustweave.credential.revocation.RevocationStatus
import kotlinx.coroutines.runBlocking
Configuration needed:
- Status list manager provider (e.g.,
inMemoryfor testing) - Revocation support enabled in credential issuance
Before You Begin
Credential revocation uses Status List 2021—a compact, efficient method for tracking revoked credentials. TrustWeave handles the complexity of status list management, bit manipulation, and blockchain anchoring (if configured).
When to use this:
- Revoking credentials that are no longer valid
- Managing credential lifecycle (expiration, suspension, revocation)
- Complying with regulations requiring revocation capabilities
- Handling security incidents (compromised credentials)
How it fits in a workflow:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.trustweave.trust.dsl.credential.RevocationProviders.IN_MEMORY
// 1. Configure revocation
val trustWeave = TrustWeave.build {
revocation { provider(IN_MEMORY) }
}
// 2. Issue credential with revocation support
val credential = trustWeave.issue {
credential { ... }
withRevocation() // Auto-creates status list
}
// 3. Revoke credential when needed
trustWeave.revoke {
credential(credential.id)
statusList(statusListId)
}
// 4. Check revocation status
val status = trustWeave.revocation {
statusList(statusListId)
}.check(credential)
Step-by-Step Guide
Step 1: Configure Revocation Support
Enable revocation in your TrustWeave configuration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.trustweave.trust.dsl.credential.KmsProviders
import org.trustweave.trust.dsl.credential.RevocationProviders
import org.trustweave.trust.dsl.credential.KeyAlgorithms.ED25519
import org.trustweave.trust.dsl.credential.DidMethods.KEY
val trustWeave = TrustWeave.build {
keys {
provider(KmsProviders.IN_MEMORY)
algorithm(ED25519)
}
did {
method(KEY) {
algorithm(ED25519)
}
}
revocation {
provider(RevocationProviders.IN_MEMORY) // For testing; use persistent provider in production
}
}
What this does:
- Configures a status list manager for tracking revoked credentials
- Enables revocation operations in TrustWeave
- Sets up the infrastructure for status list management
Note: For production, use a persistent provider (database-backed) instead of
inMemory.
Step 2: Create a Status List
Create a status list for tracking revoked credentials. Status lists are typically created automatically when issuing credentials with withRevocation(), but you can also create them explicitly:
1
2
3
4
5
6
7
val statusList = trustWeave.revocation {
forIssuer(issuerDid.value)
purpose(StatusPurpose.REVOCATION)
size(131072) // Optional: default is 131072 credentials
}.createStatusList()
println("Status list created: ${statusList.id}")
What this does:
- Creates a status list credential for the issuer
- Allocates space for tracking revoked credentials (default: 131,072)
- Returns a
StatusListCredentialthat can be referenced in credentials
Key concepts:
- Status List: A credential that contains a bitstring tracking revocation status
- Status List Index: Each credential gets an index (computed from credential ID hash)
- Bitstring: Compact representation (1 bit per credential)
Step 3: Issue Credential with Revocation Support
Issue a credential that includes a reference to the status list:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.trustweave.did.identifiers.Did
import kotlinx.datetime.Clock
val credential = trustWeave.issue {
credential {
id("https://example.edu/credentials/degree-123")
type("DegreeCredential")
issuer(issuerDid)
subject {
id(Did("did:key:student"))
"degree" {
"name" to "Bachelor of Science"
}
}
issued(Clock.System.now())
}
signedBy(issuerDid = issuerDid, keyId = keyId)
withRevocation() // Auto-creates status list if needed
}
What this does:
- Automatically creates a status list if one doesn’t exist
- Adds
credentialStatusto the credential with status list reference - Enables revocation checking during verification
The credentialStatus field will look like:
1
2
3
4
5
6
7
{
"id": "https://example.edu/statuslists/1#0",
"type": "StatusList2021Entry",
"statusPurpose": "revocation",
"statusListIndex": "0",
"statusListCredential": "https://example.edu/statuslists/1"
}
Step 4: Revoke a Credential
Revoke a credential when it’s no longer valid:
1
2
3
4
5
6
7
8
9
10
val revoked = trustWeave.revoke {
credential(credential.id ?: throw IllegalStateException("Credential must have ID"))
statusList(statusList.id)
}
if (revoked) {
println("✅ Credential revoked successfully")
} else {
println("⚠️ Credential was already revoked")
}
What this does:
- Computes the credential’s index in the status list (from credential ID hash)
- Sets the corresponding bit in the status list bitstring
- Updates the status list credential
- Returns
trueif revocation succeeded,falseif already revoked
What happens internally:
- Hash the credential ID to get index
- Set the bit at that index in the status list
- Update the status list credential’s bitstring
- Optionally anchor the updated status list to blockchain (if configured)
Step 5: Check Revocation Status
Verify if a credential is revoked:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
val status = trustWeave.revocation {
statusList(statusList.id)
}.check(credential)
when (status) {
is RevocationStatus.Revoked -> {
println("❌ Credential is revoked")
println(" Revoked at: ${status.revokedAt}")
}
is RevocationStatus.Active -> {
println("✅ Credential is active (not revoked)")
}
is RevocationStatus.Unknown -> {
println("⚠️ Revocation status unknown")
println(" Reason: ${status.reason}")
}
}
What this does:
- Extracts the status list reference from the credential
- Checks the bit at the credential’s index in the status list
- Returns a sealed result type with revocation status
Complete Example
Here’s a complete, runnable example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import org.trustweave.trust.TrustWeave
import org.trustweave.credential.model.ProofType
import org.trustweave.trust.dsl.credential.KmsProviders
import org.trustweave.trust.dsl.credential.RevocationProviders
import org.trustweave.trust.dsl.credential.KeyAlgorithms.ED25519
import org.trustweave.trust.dsl.credential.DidMethods.KEY
import org.trustweave.trust.types.getOrThrowDid
import org.trustweave.credential.results.getOrThrow
import org.trustweave.did.resolver.DidResolutionResult
import org.trustweave.did.resolver.errorMessage
import org.trustweave.did.identifiers.extractKeyId
import org.trustweave.credential.revocation.RevocationStatus
import org.trustweave.credential.revocation.StatusPurpose
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.Clock
import kotlin.time.Duration.Companion.days
fun main() = runBlocking {
// Step 1: Configure TrustWeave with revocation
val trustWeave = TrustWeave.build {
keys {
provider(KmsProviders.IN_MEMORY)
algorithm(ED25519)
}
did {
method(KEY) {
algorithm(ED25519)
}
}
revocation {
provider(RevocationProviders.IN_MEMORY)
}
}
// Step 2: Create issuer DID
val issuerDid = trustWeave.createDid { method(KEY) }.getOrThrowDid()
// Step 3: Get key ID
val issuerDocument = when (val res = trustWeave.resolveDid(issuerDid)) {
is DidResolutionResult.Success -> res.document
else -> throw IllegalStateException(res.errorMessage ?: "Failed to resolve DID")
}
val keyId = issuerDocument.verificationMethod.firstOrNull()?.extractKeyId()
?: throw IllegalStateException("No verification method found")
// Step 4: Issue credential with revocation support
val credential = trustWeave.issue {
credential {
id("https://example.edu/credentials/degree-123")
type("DegreeCredential")
issuer(issuerDid.value)
subject {
id("did:key:student")
"degree" {
"name" to "Bachelor of Science"
}
}
issued(Clock.System.now())
expires((365 * 10).days)
}
signedBy(issuerDid = issuerDid, keyId = keyId)
withRevocation() // Auto-creates status list
}
println("✅ Credential issued: ${credential.id}")
println(" Status list: ${credential.credentialStatus?.statusListCredential}")
// Step 5: Check initial status
val statusListId = credential.credentialStatus?.statusListCredential
?: throw IllegalStateException("Credential has no status list")
val initialStatus = trustWeave.revocation {
statusList(statusListId)
}.check(credential)
when (initialStatus) {
is RevocationStatus.Active -> println("✅ Credential is active")
else -> println("Status: $initialStatus")
}
// Step 6: Revoke credential
val revoked = trustWeave.revoke {
credential(credential.id ?: throw IllegalStateException("Credential must have ID"))
statusList(statusListId)
}
if (revoked) {
println("✅ Credential revoked successfully")
}
// Step 7: Verify revocation
val revokedStatus = trustWeave.revocation {
statusList(statusListId)
}.check(credential)
when (revokedStatus) {
is RevocationStatus.Revoked -> {
println("✅ Revocation confirmed: Credential is revoked")
}
else -> {
println("⚠️ Unexpected status: $revokedStatus")
}
}
}
Visual Flow Diagram
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
┌─────────────────────────────────────────────────────────────┐
│ Credential Revocation Flow │
└─────────────────────────────────────────────────────────────┘
┌──────────────┐
│ Step 1 │ Configure Revocation
│ Configure │ • Status List Manager (inMemory/database)
└──────┬───────┘ • Enable revocation support
│
▼
┌──────────────┐
│ Step 2 │ Create Status List
│ Create List │ • For issuer DID
└──────┬───────┘ • Purpose: REVOCATION
│ • Size: 131,072 (default)
▼
┌──────────────┐
│ Step 3 │ Issue Credential
│ Issue with │ • Include credentialStatus
│ Revocation │ • Reference status list
└──────┬───────┘ • Auto-create list if needed
│
▼
┌──────────────┐
│ Step 4 │ Revoke Credential
│ Revoke │ • Hash credential ID → index
└──────┬───────┘ • Set bit at index
│ • Update status list
▼
┌──────────────┐
│ Step 5 │ Check Status
│ Verify │ • Read status list
└──────┬───────┘ • Check bit at index
│ • Return RevocationStatus
▼
✅ Active
or
❌ Revoked
Verification Step
After revoking, verify the revocation status:
1
2
3
4
5
6
7
// Quick verification
val status = trustWeave.revocation {
statusList(statusListId)
}.check(credential)
val isRevoked = status is RevocationStatus.Revoked
println("Credential is ${if (isRevoked) "revoked" else "active"}")
Expected output:
1
2
3
4
5
✅ Credential issued: https://example.edu/credentials/degree-123
Status list: https://example.edu/statuslists/1
✅ Credential is active
✅ Credential revoked successfully
✅ Revocation confirmed: Credential is revoked
What to check:
- Credential has
credentialStatusfield - Status list ID is valid
- Revocation operation returns
true - Status check returns
RevocationStatus.Revoked
Common Errors & Troubleshooting
Error: “Status list manager is not configured”
Problem: Revocation provider wasn’t configured in TrustWeave.
Solution:
1
2
3
4
5
6
7
import org.trustweave.trust.dsl.credential.RevocationProviders.IN_MEMORY
// ✅ Ensure revocation is configured
val trustWeave = TrustWeave.build {
revocation {
provider(IN_MEMORY) // or your provider
}
}
Error: “Status list ID is required”
Problem: The statusList() method wasn’t called in the revocation builder.
Solution:
1
2
3
4
5
6
7
8
9
10
11
// ❌ Missing status list ID
trustWeave.revoke {
credential(credentialId)
// Missing: statusList(statusListId)
}
// ✅ Correct
trustWeave.revoke {
credential(credentialId)
statusList(statusListId) // Required
}
Error: “Credential has no status list”
Problem: The credential was issued without revocation support.
Solution:
1
2
3
4
5
6
// ✅ Issue credential with revocation
val credential = trustWeave.issue {
credential { ... }
signedBy(issuerDid = issuerDid, keyId = keyId)
withRevocation() // Required for revocation support
}
Warning: Status list not found
Problem: The status list ID doesn’t exist or was deleted.
Solution:
1
2
3
4
5
6
7
8
9
// ✅ Verify status list exists
val statusList = trustWeave.revocation {
statusList(statusListId)
}.getStatusList()
if (statusList == null) {
println("Status list not found: $statusListId")
// Create new status list or use existing one
}
Error: Revocation check returns Unknown
Problem: The credential doesn’t have a status list reference, or the status list can’t be accessed.
Solution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ✅ Check credential has status list reference
if (credential.credentialStatus == null) {
println("Credential has no revocation support")
// Re-issue credential with withRevocation()
}
// ✅ Verify status list is accessible
val statusList = trustWeave.revocation {
statusList(statusListId)
}.getStatusList()
if (statusList == null) {
println("Status list not accessible: $statusListId")
}
Advanced Patterns
Pattern 1: Batch Revocation
Revoke multiple credentials at once:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val credentialIds = listOf("cred-1", "cred-2", "cred-3")
val results = credentialIds.map { credId ->
try {
val revoked = trustWeave.revoke {
credential(credId)
statusList(statusListId)
}
credId to revoked
} catch (error: Exception) {
credId to false
}
}
val successCount = results.count { it.second }
println("Revoked $successCount out of ${credentialIds.size} credentials")
Pattern 2: Suspension (Temporary Revocation)
Suspend a credential temporarily:
1
2
3
4
5
6
7
8
9
10
11
// Create suspension status list
val suspensionList = trustWeave.revocation {
forIssuer(issuerDid.value)
purpose(StatusPurpose.SUSPENSION) // Different purpose
}.createStatusList()
// Suspend credential
trustWeave.revocation {
credential(credential.id ?: throw IllegalStateException("Credential must have ID"))
statusList(suspensionList.id)
}.suspend()
Pattern 3: Revocation with Verification
Revoke and immediately verify:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Revoke
val revoked = trustWeave.revoke {
credential(credential.id ?: throw IllegalStateException("Credential must have ID"))
statusList(statusListId)
}
if (revoked) {
// Immediately verify revocation
val status = trustWeave.revocation {
statusList(statusListId)
}.check(credential)
when (status) {
is RevocationStatus.Revoked -> {
println("✅ Revocation confirmed")
}
else -> {
println("⚠️ Revocation not yet reflected: $status")
}
}
}
Choosing a Status List Manager
The revocation { provider(IN_MEMORY) } block in the examples above is fine for tests, but production deployments should pick a concrete status-list format and a persistent backend. Two interoperable formats ship as plugins, each auto-discovered via SPI under a registered provider name.
W3C Bitstring Status List
The default W3C format. Status lists are themselves Verifiable Credentials containing a gzipped, base64url-encoded bitstring. Best for VC-LD / VC-JWT credentials.
- Module: credentials/plugins/status-list/bitstring — see the plugin README
- Provider name:
"bitstring" - Spec: W3C Bitstring Status List v1.0
- Use when: issuing W3C VCs (
vc-ld,vc-jwt).
Option A — SPI-driven (configure via system properties):
1
2
3
4
5
6
7
8
// On the JVM, set these before TrustWeave.build:
// -Dtrustweave.statuslist.jdbc.url=jdbc:postgresql://...
// -Dtrustweave.statuslist.jdbc.username=...
// -Dtrustweave.statuslist.jdbc.password=...
// -Dtrustweave.statuslist.issuer.did=did:web:issuer.example.com
val trustWeave = TrustWeave.build {
revocation { provider("bitstring") }
}
Option B — construct the manager directly for full control:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.trustweave.revocation.bitstring.BitstringStatusListManagerFactory
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
val dataSource = HikariDataSource(HikariConfig().apply {
jdbcUrl = "jdbc:postgresql://localhost:5432/trustweave"
username = "trustweave"
password = System.getenv("DB_PASSWORD")
})
val manager = BitstringStatusListManagerFactory.create(
dataSource = dataSource,
kms = kms,
issuerDid = "did:web:issuer.example.com",
bitsPerEntry = 1
)
// Wire `manager` into your own services as a CredentialRevocationManager.
IETF Token Status List
JWT/CWT-based status list designed for SD-JWT VC and ISO mdoc. Status is encoded as a signed token rather than a VC, so it fits the SD-JWT VC status claim and mdoc flows naturally.
- Module: credentials/plugins/status-list/token — see the plugin README
- Provider name:
"token" - Spec: IETF Token Status List
- Use when: issuing SD-JWT VCs or mdocs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// SPI route — additionally honors -Dtrustweave.statuslist.token.uri
val trustWeave = TrustWeave.build {
revocation { provider("token") }
}
// Or build directly:
import org.trustweave.revocation.token.TokenStatusListManagerFactory
val manager = TokenStatusListManagerFactory.create(
dataSource = dataSource,
kms = kms,
issuerDid = "did:web:issuer.example.com",
statusListUri = "https://issuer.example.com/statuslists/1",
bitsPerEntry = 1
)
Note: Both SPI providers require a
KeyManagementServiceto be injected before the framework callscreate(). The TrustWeaverevocation { provider("bitstring") }DSL handles this when KMS is configured in the sameTrustWeave.build { keys { ... } }block.
Publishing Status Lists
For verifiers to check revocation, the signed status-list credential (or token) must be reachable at the URL embedded in the credential’s credentialStatus entry. The status-list/publishing module provides four backends behind a common StatusListPublisher interface — see the plugin README for full details.
| Backend | Use when |
|---|---|
LocalFileStatusListPublisher |
Local dev / testing, or behind your own web server |
S3StatusListPublisher |
Hosting on AWS S3 (optionally fronted by CloudFront) |
AzureBlobStatusListPublisher |
Hosting on Azure Blob Storage |
GcsStatusListPublisher |
Hosting on Google Cloud Storage |
The interface is:
1
2
3
4
5
interface StatusListPublisher {
suspend fun publish(statusListId: String, content: ByteArray, contentType: String): String
suspend fun delete(statusListId: String)
suspend fun getUrl(statusListId: String): String?
}
Example — set up an S3 publisher and push a serialized status list:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.trustweave.revocation.publishing.S3StatusListPublisher
import org.trustweave.revocation.publishing.S3PublisherConfig
import kotlinx.coroutines.runBlocking
val publisher = S3StatusListPublisher(
S3PublisherConfig(
bucket = "trust.example.com",
region = "us-east-1",
keyPrefix = "statuslists/",
publicUrlPattern = "https://trust.example.com/statuslists/{id}"
)
)
runBlocking {
// `bytes` is the signed status list VC (Bitstring) or status token (Token),
// produced by the corresponding manager.
val url = publisher.publish(
statusListId = "sl-001",
content = bytes,
contentType = "application/json" // or "application/jwt" for Token Status List
)
println("Published at: $url")
}
Tip: Republish whenever a status list changes. Verifiers fetch the URL with normal HTTP caching, so use short
Cache-Controlheaders on the published object.
Verifier-Side Status Checking
On the verifier side, the CredentialStatusChecker SPI gates verification on a live status fetch — proof engines call it before returning a Valid result.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.trustweave.credential.spi.status.CredentialStatusChecker
import org.trustweave.credential.spi.status.CredentialStatusCheckResult
import org.trustweave.credential.model.vc.VerifiableCredential
class HttpStatusChecker : CredentialStatusChecker {
override suspend fun checkStatus(
credential: VerifiableCredential
): CredentialStatusCheckResult {
val entry = credential.credentialStatus
?: return CredentialStatusCheckResult.NoStatus
// Fetch entry.statusListCredential, decode the bitstring,
// check the bit at entry.statusListIndex, then return one of:
// Valid | Revoked(reason) | Suspended(reason) | CheckFailed(reason)
return CredentialStatusCheckResult.Valid
}
}
Inject it via the proof engine config under the "statusChecker" key:
1
2
3
4
5
import org.trustweave.credential.spi.proof.ProofEngineConfig
val config = ProofEngineConfig(
properties = mapOf("statusChecker" to HttpStatusChecker())
)
The sealed result type covers every outcome a verifier needs to surface:
| Result | Meaning |
|---|---|
Valid |
Status list confirms the credential is active |
Revoked(reason) |
Credential has been permanently revoked |
Suspended(reason) |
Credential is temporarily on hold |
CheckFailed(reason) |
Status check itself failed (network, malformed list) |
NoStatus |
Credential has no credentialStatus field |
Next Steps
Now that you can revoke credentials, here are ways to extend your implementation:
1. Integrate with Verification
Check revocation during credential verification:
1
2
3
4
val result = trustWeave.verify {
credential(credential)
checkRevocation() // Automatically checks status list
}
See: How to Verify Credentials
2. Blockchain-Anchored Revocation
Anchor status lists to blockchain for tamper evidence:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.trustweave.trust.dsl.credential.RevocationProviders.IN_MEMORY
// Configure blockchain anchoring
val trustWeave = TrustWeave.build {
anchor {
chain("algorand:testnet") {
inMemory()
}
}
revocation {
provider(IN_MEMORY)
}
}
// Status lists will be anchored automatically when threshold is reached
See: How to Anchor to Blockchain
3. Revocation Webhooks
Set up notifications when credentials are revoked:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Custom revocation handler
fun handleRevocation(credentialId: String) {
// Notify holders, update database, etc.
webhookService.notifyRevocation(credentialId)
}
// Revoke with notification
val revoked = trustWeave.revoke {
credential(credentialId)
statusList(statusListId)
}
if (revoked) {
handleRevocation(credentialId)
}
4. Revocation Analytics
Track revocation patterns:
1
2
3
4
5
6
7
// Get status list statistics
val statusList = trustWeave.revocation {
statusList(statusListId)
}.getStatusList()
// Analyze revocation patterns
// (Implementation depends on status list manager)
Summary
You’ve learned how to revoke credentials with TrustWeave:
✅ Configured revocation support with status list manager
✅ Created status lists for tracking revoked credentials
✅ Issued credentials with revocation using withRevocation()
✅ Revoked credentials when no longer valid
✅ Checked revocation status to verify credentials
Key takeaways:
- Status List 2021 provides efficient, compact revocation tracking
- TrustWeave handles bit manipulation and status list management automatically
- Revocation is checked during verification when
checkRevocation()is enabled - Status lists can be anchored to blockchain for additional tamper evidence
What’s next:
- Integrate revocation checking into verification workflows
- Set up blockchain anchoring for status lists
- Implement revocation notifications and analytics
- Explore suspension for temporary revocation
For more examples, see the scenarios documentation.