Credential Issuance — Service Reference
Module: backends/credential-issuance · REST: 8081 · gRPC: 50051
Architecture
What it does
- Single credential issuance — real Ed25519 signing: via Issuer Registry’s
SignPayloadRPC for CUSTODIAL issuers, or local verification of a caller-supplied BYOK signature against the issuer’s registered public key. Not a KMS placeholder. - Credential retrieval by internal UUID or public
shareable_id(oneoflookup) — backs authenticated lookups and the public verification-link flow. - Credential claiming — links a placeholder-holder-DID credential to a real wallet account
once the holder authenticates. Idempotent: re-claiming by the rightful owner returns
ALREADY_CLAIMED, not an error. - Revocation with issuer-ownership verification — checks the caller’s issuer UUID (or resolved
DID) against
credential.issuer_id; supports a reason plus an LLM-assistedrevocation_categoryclassification. - 8 read-path RPCs backing the issuer portal, holder wallet, and network dashboards — so Core
Engine reads
wallet.credentialsexclusively through gRPC instead of a direct JDBC read.
There is no batch RPC. POST /api/v1/credentials/batch exists only as a Core Engine REST
endpoint that loops over this service’s single IssueCredential RPC once per record.
RPC surface (credential.proto)
service CredentialIssuanceService {
rpc IssueCredential(IssueCredentialRequest) returns (IssueCredentialResponse);
rpc RevokeCredential(RevokeCredentialRequest) returns (RevokeCredentialResponse);
rpc GetCredential(GetCredentialRequest) returns (GetCredentialResponse);
rpc ClaimCredential(ClaimCredentialRequest) returns (ClaimCredentialResponse);
// Read-path — issuer portal / holder wallet / network dashboards
rpc ListCredentialsByIssuer(ListCredentialsByIssuerRequest) returns (ListCredentialsResponse);
rpc GetCredentialForIssuer(GetCredentialForIssuerRequest) returns (CredentialRecord);
rpc GetIssuerCredentialStats(GetIssuerCredentialStatsRequest) returns (IssuerCredentialStatsResponse);
rpc GetCredentialForHolder(GetCredentialForHolderRequest) returns (CredentialRecord);
rpc SearchCredentialsByHolder(SearchCredentialsByHolderRequest) returns (ListCredentialsResponse);
rpc GetHolderCredentialStats(GetHolderCredentialStatsRequest) returns (HolderCredentialStatsResponse);
rpc ListNewlyExpiredCredentials(ListNewlyExpiredCredentialsRequest) returns (ListCredentialsResponse);
rpc GetNetworkCredentialStats(GetNetworkCredentialStatsRequest) returns (NetworkCredentialStatsResponse);
}IssueCredentialRequest also carries optional BYOK fields (signature, key_id,
credential_id, issuance_timestamp) and soft FKs (learner_id, catalog_entry_version_id,
outcome_id). GetCredentialRequest is a oneof lookup (credential_id or shareable_id).
Error codes
| RPC | Codes |
|---|---|
issueCredential | INVALID_ARGUMENT, ALREADY_EXISTS (duplicate BYOK credential_id), INTERNAL, or whatever status Issuer Registry’s SignPayload failed with (preserved, not masked) |
revokeCredential | INVALID_ARGUMENT, NOT_FOUND, PERMISSION_DENIED, INTERNAL |
getCredential | INVALID_ARGUMENT, NOT_FOUND, INTERNAL |
claimCredential | CLAIMED, ALREADY_CLAIMED, NOT_FOUND, DID_MISMATCH (statuses, not gRPC codes) |
Server configuration
grpc:
server:
port: 50051
enable-keep-alive: true
keep-alive-time: 30s
permit-keep-alive-without-calls: true
max-inbound-message-size: 10485760HTTP/2 via gRPC netty-shaded, zero-copy for binary data, connection multiplexing, backpressure
handling, graceful shutdown (10s timeout). GrpcLogInterceptor logs method name, latency, and
response status/errors via SLF4J.
Test coverage
Unit tests cover 5 of the 12 RPCs — the 4 core issuance/lifecycle methods (minus claimCredential)
plus revoke/get:
testIssueCredentialSuccess
testIssueCredentialMissingIssuerDid
testGetCredentialSuccess
testGetCredentialNotFound
testRevokeCredentialSuccessclaimCredential and all 8 read-path RPCs are untested.
Performance — targets, not measurements
No load-test harness exists for this service. Figures below are original-authoring estimates:
| Operation | Latency | Notes |
|---|---|---|
| Single issuance | 45–100 ms | DB + JSON construction + signing round-trip |
| Credential retrieval | 10–20 ms | Database lookup |
| Credential revocation | 30–50 ms | Database update |
Security
- Real Ed25519 signing — CUSTODIAL via Issuer Registry’s
SignPayload; BYOK verified locally against the issuer’s registered public key. - Revocation checks that the calling issuer actually owns the credential being revoked.
- No authentication/authorization at this service’s own gRPC layer — relies on network isolation; Core Engine/API Gateway handle caller auth upstream. mTLS between services is the recommended (and, per Internal gRPC mTLS, actually implemented) control.
Manual testing
grpcurl -plaintext \
-d '{"issuer_did":"did:key:issuer123","holder_did":"did:key:holder456","credential_type":"SecondaryEducationCredential"}' \
localhost:50051 \
com.attestpro.credential.CredentialIssuanceService/IssueCredentialWhat’s not implemented
- Batch issuance as a gRPC RPC (see above — it’s Core Engine’s REST loop instead).
- Issuer authorization validation via JWT claims at this service’s own gRPC layer.
- gRPC reflection, request-validation interceptor, circuit breaker for DB failures, custom error details, gRPC compression, distributed tracing, load-test harness.
- Swapping Issuer Registry’s application-layer AES-256-GCM custodial key encryption for a real HSM/KMS (see CLAUDE.md §Security & Key Management) — signing itself is already real.
See Integrate a Credential Issuance Client for the Core Engine-side integration walkthrough.