Skip to main content

Revocation Best Practices

This guide provides strategies, patterns, and recommendations for managing credential revocation effectively at scale.

Strategic Planning

Define Your Revocation Policy

Before issuing credentials, establish clear policies:

When to Revoke:

  • Employee termination or role change
  • Security breach or compromise
  • Credential error or invalid data
  • Qualification expiration
  • Policy violation
  • Holder request

Who Can Revoke:

  • Define roles and permissions
  • Implement approval workflows for sensitive credentials
  • Audit all revocation actions
  • Separate test and production environments

Response Times:

  • Critical security: Immediate (< 5 minutes)
  • Standard termination: Same business day
  • Administrative correction: Within 24 hours
  • Batch cleanup: Weekly or monthly

Document Your Processes

Create internal documentation covering:

  • Step-by-step revocation procedures
  • Escalation paths for urgent revocations
  • Communication templates for holders
  • Audit trail requirements
  • Compliance reporting

Technical Best Practices

Design for Revocation from the Start

During Credential Design:

// Always include status metadata when issuing
const credential = await issueCredential({
template: "EmployeeCredential",
attributes: {...},
// Enable revocation
includeStatus: true,
statusPurpose: "revocation"
});

Use Separate Lists by Type:

// Separate status lists for different credential types
const employeeListId = await allocateStatusList({
issuerDid: orgDid,
vct: "EmployeeCredential",
purpose: "revocation"
});

const certificationListId = await allocateStatusList({
issuerDid: orgDid,
vct: "CertificationCredential",
purpose: "revocation"
});

Monitor List Capacity

Set up alerts when lists reach capacity:

// Check list utilization
const list = await StatusListModel.findById(listId);
const used = countUsedIndices(list.bits);
const utilizationPercent = (used / list.length) * 100;

if (utilizationPercent > 80) {
await notifyOps({
alert: "Status list nearing capacity",
listId: listId,
utilization: utilizationPercent
});
}

Implement Rate Limiting

Prevent abuse with rate limits:

// Limit revocations per issuer per hour
const revocationsLastHour = await getRevocationCount({
issuerDid: orgDid,
since: Date.now() - 3600000
});

if (revocationsLastHour > 100) {
throw new Error("Revocation rate limit exceeded");
}

Use Idempotent Operations

Handle duplicate revocation requests gracefully:

// Check if already revoked before updating
const binding = await StatusBindingModel.findOne({
credentialId: sessionId
});

if (binding.revoked) {
return {
success: true,
message: "Credential already revoked",
revokedAt: binding.updatedAt
};
}

// Proceed with revocation
await revoke(binding.listId, binding.statusListIndex);

Operational Best Practices

Establish Monitoring

Track key metrics continuously:

Revocation Metrics:

  • Total revocations per day/week/month
  • Revocation by reason/category
  • Time from issue to revocation
  • Failed revocation attempts
  • List utilization trends

Performance Metrics:

  • Revocation API latency
  • Status list fetch time
  • Cache hit ratios
  • Error rates

Security Metrics:

  • Unusual revocation patterns
  • Unauthorized access attempts
  • Cross-project revocation attempts

Implement Alerting

Set up alerts for:

alerts:
- name: High Revocation Rate
condition: revocations_per_hour > 50
severity: warning

- name: Status List Full
condition: list_utilization > 95%
severity: critical

- name: Failed Revocations
condition: failed_revocations > 5 in 10m
severity: warning

- name: Unauthorized Access
condition: 403_errors > 10 in 1m
severity: critical

Regular Audits

Schedule periodic reviews:

Weekly:

  • Review all revocations from past week
  • Verify proper authorization
  • Check for anomalies or patterns
  • Validate audit logs

Monthly:

  • Analyze revocation trends
  • Review list utilization
  • Assess policy effectiveness
  • Update procedures as needed

Quarterly:

  • Comprehensive security review
  • Compliance audit
  • Performance optimization
  • Training updates

Credential Lifecycle Management

Pre-Revocation Checks

Before revoking, verify:

  1. Correct Credential: Confirm credential ID and recipient
  2. Authorization: Verify requester has permission
  3. Impact Assessment: Understand downstream effects
  4. Communication Plan: How will holder be notified?

Revocation Execution

Follow a consistent process:

async function revokeCredentialWorkflow(
credentialId: string,
reason: string,
requestedBy: string
) {
// 1. Pre-revocation validation
const credential = await validateCredential(credentialId);
await checkAuthorization(requestedBy, credential.projectId);

// 2. Log the revocation intent
await auditLog.create({
action: "revocation_initiated",
credentialId,
reason,
requestedBy,
timestamp: new Date()
});

// 3. Perform revocation
const result = await revokeCredential(credentialId);

// 4. Update internal systems
await updateCRM({
credentialId,
status: "revoked",
revokedAt: new Date(),
reason
});

// 5. Notify stakeholders
await notifyStakeholders({
credentialId,
recipient: credential.recipientEmail,
reason
});

// 6. Log completion
await auditLog.create({
action: "revocation_completed",
credentialId,
result,
timestamp: new Date()
});

return result;
}

Post-Revocation Actions

After revocation:

  1. Verify Success: Check status bit updated correctly
  2. Update Records: Mark in internal systems
  3. Notify Holder: Send revocation notice if appropriate
  4. Document: Record reason and context
  5. Monitor: Watch for verification attempts

Communication Strategies

Holder Notification

When notifying holders of revocation:

Email Template:

Subject: Important: Your [Credential Type] has been revoked

Dear [Name],

We're writing to inform you that your [Credential Type]
issued on [Issue Date] has been revoked as of [Revocation Date].

Reason: [Brief explanation]

What this means:
- The credential can no longer be used for verification
- It will fail any verification checks
- You should remove it from your wallet

Next Steps:
[If applicable: Instructions for re-issuance or appeal]

If you have questions, please contact [Support Contact].

Best regards,
[Organization Name]

In-App Notification:

  • Push notification to wallet (if supported)
  • Email with clear subject line
  • SMS for critical revocations
  • Dashboard notification in your system

Internal Communication

Keep teams informed:

For HR/Admin:

Employee Credential Revoked
- Employee: Alice Smith
- Credential: Employee Badge #12345
- Revoked: 2024-01-15 10:30 AM
- Reason: Employment terminated
- Revoked by: admin@company.com
- Verification Status: Failed (as expected)

For Security Team:

Security Alert: Credential Revoked
- Type: Emergency revocation
- Reason: Suspected compromise
- Scope: 1 credential revoked
- Impact: Immediate verification failure
- Actions: Monitor for usage attempts

Audit Trail Requirements

Maintain comprehensive logs:

interface RevocationAuditLog {
timestamp: Date;
action: "revocation_initiated" | "revocation_completed";
credentialId: string;
statusListId: string;
statusListIndex: number;
reason: string;
requestedBy: string;
approvedBy?: string;
ipAddress: string;
userAgent: string;
success: boolean;
errorMessage?: string;
}

Data Retention

Define retention policies:

  • Active Credentials: Keep full audit trail
  • Revoked Credentials: Retain per compliance (e.g., 7 years)
  • Status Lists: Archive when all credentials expired/revoked
  • Audit Logs: Permanent retention for compliance

Right to Erasure

Handle data deletion requests:

async function handleErasureRequest(credentialId: string) {
// 1. Revoke credential if not already revoked
const binding = await StatusBindingModel.findOne({
credentialId
});

if (!binding.revoked) {
await revoke(binding.listId, binding.statusListIndex);
}

// 2. Anonymize personal data
await CredentialModel.updateOne(
{ issuanceSessionId: credentialId },
{
recipientEmail: "[redacted]",
attributes: { _redacted: true }
}
);

// 3. Keep revocation record for compliance
// (Status binding remains for audit)

// 4. Log erasure action
await auditLog.create({
action: "data_erasure",
credentialId,
timestamp: new Date()
});
}

Scaling Strategies

Batch Revocations

For large-scale revocations:

async function batchRevoke(credentialIds: string[]) {
const BATCH_SIZE = 100;

for (let i = 0; i < credentialIds.length; i += BATCH_SIZE) {
const batch = credentialIds.slice(i, i + BATCH_SIZE);

// Process in parallel within batch
await Promise.all(
batch.map(id => revokeCredential(id))
);

// Brief delay between batches
await sleep(1000);
}
}

Scheduled Revocations

For planned revocations:

// Schedule future revocation
await scheduleRevocation({
credentialId: "session_123",
revokeAt: new Date("2024-12-31T23:59:59Z"),
reason: "Employment contract end date"
});

// Cron job to process scheduled revocations
cron.schedule("* * * * *", async () => {
const due = await ScheduledRevocation.find({
revokeAt: { $lte: new Date() },
processed: false
});

for (const scheduled of due) {
await revokeCredential(scheduled.credentialId);
scheduled.processed = true;
await scheduled.save();
}
});

Automatic Expiration Cleanup

Revoke expired credentials:

// Find and revoke expired credentials
async function cleanupExpired() {
const expired = await CredentialModel.find({
expiryDate: { $lt: new Date() },
state: { $ne: "Revoked" }
});

for (const credential of expired) {
await revokeCredential(credential.issuanceSessionId);

await auditLog.create({
action: "automatic_expiry_revocation",
credentialId: credential.issuanceSessionId,
timestamp: new Date()
});
}
}

// Run daily
cron.schedule("0 2 * * *", cleanupExpired);

Error Handling

Graceful Degradation

Handle failures gracefully:

async function safeRevoke(credentialId: string) {
try {
await revokeCredential(credentialId);
return { success: true };
} catch (error) {
// Log error
logger.error("Revocation failed", {
credentialId,
error: error.message
});

// Notify ops team
await notifyOps({
alert: "Revocation failure",
credentialId,
error: error.message
});

// Return partial success if possible
return {
success: false,
error: error.message,
retryable: isRetryable(error)
};
}
}

Retry Logic

Implement exponential backoff:

async function revokeWithRetry(
credentialId: string,
maxRetries = 3
) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await revokeCredential(credentialId);
} catch (error) {
if (attempt === maxRetries - 1) throw error;

const delay = Math.pow(2, attempt) * 1000;
await sleep(delay);
}
}
}

Testing and Validation

Test Revocation Flow

Include in your test suite:

describe("Credential Revocation", () => {
it("should revoke credential successfully", async () => {
// Issue credential
const credential = await issueCredential({...});

// Verify it's active
const status1 = await checkStatus(credential.id);
expect(status1.revoked).toBe(false);

// Revoke credential
await revokeCredential(credential.id);

// Verify it's revoked
const status2 = await checkStatus(credential.id);
expect(status2.revoked).toBe(true);
});

it("should fail verification after revocation", async () => {
const credential = await issueCredential({...});
await revokeCredential(credential.id);

const verification = await verifyCredential(credential);
expect(verification.valid).toBe(false);
expect(verification.reason).toContain("revoked");
});

it("should handle idempotent revocation", async () => {
const credential = await issueCredential({...});

// Revoke twice
await revokeCredential(credential.id);
await revokeCredential(credential.id);

// Should not throw error
const status = await checkStatus(credential.id);
expect(status.revoked).toBe(true);
});
});

Next Steps