Skip to content
Registry Stack Docs Latest

Credential lifecycle and status

View as Markdown

Registry Notary issues SD-JWT VC credentials with explicit expiry. Live credential status is optional. This guide explains the default lifecycle, when to enable status, what status means, and how operators should run it.

By default, issued credentials are status-free:

  • No status claim is added to the SD-JWT VC payload.
  • No revocation list or status-list profile is published.
  • Verifiers rely on issuer trust, holder binding, and credential expiry.
  • Credential profiles default to 600 seconds of validity when validity_seconds is omitted; wallet-facing profiles should set an explicit validity period that matches the use case.

The top-level evidence.max_credential_validity_seconds sets the issuing agency’s local ceiling, and profile validity must be between 1 second and that maximum. Self-attestation profiles are also bounded by self_attestation.token_policy.max_credential_validity_seconds.

This lets operators keep offer codes, access tokens, proofs, and evidence freshness short while issuing wallet-held credentials with a practical validity window. Deployments that need longer-lived credentials should enable live status or another revocation/lifecycle surface.

Enable credential status when verifiers need to check a live lifecycle state after issuance, for example:

  • A credential may need temporary suspension.
  • A credential may need revocation before its expiry.
  • A verifier policy requires live issuer status.
  • An operator wants a bounded audit trail for lifecycle actions.

Do not enable status only because it is familiar from other credential systems. It adds an online dependency for verifiers and creates an operational store that must be kept available for the retention window.

credential_status:
enabled: true
base_url: https://notary.example.gov
storage: redis
retention_seconds: 86400
redis:
url_env: REGISTRY_NOTARY_STATUS_REDIS_URL
key_prefix: registry-notary
connect_timeout_ms: 1000
operation_timeout_ms: 500

Fields:

  • enabled: adds status records during issuance and includes a status claim in issued credentials.
  • base_url: public HTTPS origin used to build status URLs. Use plain HTTP only for local or lab deployments.
  • storage: in_memory or redis.
  • retention_seconds: how long status records are retained.
  • redis.url_env: environment variable containing the Redis URL.
  • redis.key_prefix: deployment-specific key prefix.
  • connect_timeout_ms and operation_timeout_ms: Redis timeout controls.

Use in_memory only for local or lab flows. It is process-local and disappears on restart. Use Redis when more than one process can issue credentials, when rolling deploys overlap traffic, or when status must survive restart.

When status is enabled, issued SD-JWT VC payloads include a status object using the Registry Notary credential status profile. The status URL is anchored at the public base URL:

{
"status": {
"type": "RegistryNotaryCredentialStatus",
"statusUrl": "https://notary.example.gov/v1/credentials/urn:ulid:01HX.../status"
}
}

The status URL is intentionally per credential. It is not a status list and it does not expose subject identifiers or claim values.

The public status response can report:

StatusMeaning
validThe credential has an active status record and is not expired.
suspendedThe operator has temporarily disabled the credential.
revokedThe operator has permanently revoked the credential.
expiredThe credential lifetime has passed. This can be derived from the stored expiry.

Only valid, suspended, and revoked are mutable lifecycle states. expired is derived from time.

stateDiagram-v2
  [*] --> valid: issuance creates status record
  valid --> suspended: admin suspend
  suspended --> valid: admin reinstate
  valid --> revoked: admin revoke
  suspended --> revoked: admin revoke
  valid --> expired: validity window passes
  suspended --> expired: validity window passes
  revoked --> [*]
  expired --> [*]

  note right of revoked
    Permanent, no return path
  end note
  note right of expired
    Derived from stored expiry,
    not an admin action
  end note

Credential status lifecycle. Admin mutation moves a credential between valid, suspended, and revoked; expired is derived from the stored expiry rather than set by an operator.

Status records intentionally contain lifecycle metadata only:

  • Credential id.
  • Issuer or service metadata needed by Notary.
  • Credential profile id.
  • Issued-at and expires-at timestamps.
  • Last-updated timestamp.
  • Current lifecycle status.

Status records must not contain:

  • Subject ids.
  • Holder DIDs or holder public keys.
  • Claim values.
  • SD-JWT disclosures.
  • Source rows.
  • Raw access tokens or proof JWTs.

This lets a verifier check lifecycle state without turning the status store into a second registry of personal data.

Status operations are exposed as:

  • Public status retrieval at the credential’s status URL.
  • Admin status mutation for operators with registry_notary:admin.

Use the SDK methods where possible so your integration does not depend on route names directly:

  • Rust: credential_status(...) and update_credential_status(...).
  • Node.js and Python wrappers expose only the read-only status lookup (credentialStatus / credential_status); the admin status mutation is available via Rust or HTTP only.

Admin mutation accepts a new status value of valid, suspended, or revoked.

Credential issuance creates the status record after the credential id and expiry are known. If the status store cannot write the record, issuance should fail closed rather than issuing a credential that references a missing live status URL.

Status retrieval should be treated as public verifier traffic. Status mutation is an admin operation and should be limited to trusted operator tooling.

Readiness should fail when a configured Redis status backend is unavailable. That is preferable to issuing status-bearing credentials that cannot be checked or updated reliably.

Set retention_seconds to cover:

  • Maximum credential validity.
  • Expected verifier clock skew.
  • Any grace period required by the relying party.
  • Audit or dispute window for lifecycle actions.

For the current 600-second credential ceiling, 24 hours is usually enough for test and pilot deployments. Production policies may require longer status retention even though the credential itself is short lived.

Do not shorten retention while outstanding credentials still reference status URLs unless verifiers have agreed to treat missing status records as expired or invalid.

Verifier policy should be explicit:

  • Accept status-free credentials only from profiles that are expected to be status-free.
  • For status-bearing credentials, fetch the status URL and require valid.
  • Treat suspended, revoked, missing status, malformed status, or network failure according to the relying party’s risk policy. High-assurance flows should fail closed.
  • Apply credential expiry even when status returns valid.

Registry Notary does not currently publish StatusList or external revocation list profiles. The supported status profile is documented in sd-jwt-vc-conformance-profile.md.

  • Confirm every credential profile that needs status is issued by a deployment with credential_status.enabled: true.
  • Confirm credential_status.base_url is the public issuer URL verifiers can reach.
  • Use Redis for deployable multi-process status.
  • Keep the status Redis separate or key-prefixed from unrelated applications.
  • Confirm /ready fails when the status backend is unavailable.
  • Run one credential issuance and verify the payload has a status URL.
  • Fetch that status URL and confirm valid.
  • Mutate a test credential to suspended or revoked with an admin credential.
  • Confirm audit records exist for issuance and mutation without raw personal data.
SymptomLikely causeCheck
Issued credential has no status claimcredential_status.enabled is false in the issuing deploymentCheck expanded config
Status URL points at localhostcredential_status.base_url was copied from a local configSet public HTTPS base URL
Status is missing after restartstorage: in_memory was usedUse Redis for shared or durable status
Admin update is unauthorizedMissing registry_notary:admin scopeCheck caller scopes or OIDC scope mapping
Verifier sees expired quicklyProfile validity is short or clocks differCheck validity_seconds, verifier clock, and expiry policy
Readiness fails after enabling statusRedis URL env var missing or backend unreachableCheck credential_status.redis.url_env and Redis connectivity