Credentials
What's actually inside a signed LearnCoin credential — every field, what it means, and why it's there.
A LearnCoin credential is a JSON-LD document that is simultaneously three things at the protocol level:
- A W3C Verifiable Credential 2.0 envelope
- A Blockcerts v3 credential (with
MerkleProof2019proof) - An Open Badges 3.0 (
OpenBadgeCredential) with AchievementSubject shape
All three live in the same document — not separate representations. A verifier built against any one of the three specs can consume a LearnCoin credential without custom code.
A full signed credential — annotated
Below is a complete credential as it would be returned from GET /v1/credentials/{id} or embedded in the public verification page.
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/blockcerts/v3",
"https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json",
"https://w3id.org/security/data-integrity/v2"
],
"id": "urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"id": "did:web:learncoin.me#tenant-01HXYZ",
"type": ["Profile"],
"name": "Example University"
},
"issuanceDate": "2026-04-23T12:00:00Z",
"expirationDate": "2031-04-23T12:00:00Z",
"credentialSubject": {
"id": "urn:uuid:4e0c7f2e-6b1a-4f5a-9c8e-8a1b2c3d4e5f",
"type": ["AchievementSubject"],
"name": "Ada Lovelace",
"achievement": {
"id": "https://example.edu/credentials/intro-ml",
"type": ["Achievement"],
"name": "Introduction to Machine Learning",
"description": "Completed the 12-week introductory course with distinction.",
"criteria": {
"narrative": "Passing grade (≥ 70%) on final project plus peer-reviewed submission."
},
"alignment": [
{
"type": ["Alignment"],
"targetFramework": "ESCO",
"targetCode": "S1.4.0",
"targetName": "Machine learning",
"targetUrl": "https://esco.ec.europa.eu/en/classification/skill?uri=http%3A%2F%2Fdata.europa.eu%2Fesco%2Fskill%2FS1.4.0"
}
]
}
},
"proof": {
"type": "MerkleProof2019",
"created": "2026-04-23T12:00:58.101Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:web:learncoin.me#tenant-01HXYZ-key-1",
"proofValue": "z4KjzXtRhZg…"
}
}Field-by-field
@context
The canonical JSON-LD contexts, in order:
https://www.w3.org/ns/credentials/v2— the W3C VC 2.0 core context. DefinesVerifiableCredential,issuer,credentialSubject,proof.https://w3id.org/blockcerts/v3— the Blockcerts v3 context. DefinesMerkleProof2019and the chain-anchoring vocabulary.https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json— the Open Badges 3.0 context. DefinesOpenBadgeCredential,Achievement,AchievementSubject,Alignment.https://w3id.org/security/data-integrity/v2— Linked Data Proofs canonicalization context.
The order matters for JSON-LD processing — don't reorder. Verifiers that don't fetch contexts at runtime (for performance) use local copies keyed on these exact URLs.
id
A stable URL or URN identifying this credential instance. LearnCoin mints urn:uuid:<v4> for every credential. Not to be confused with credentialSubject.id (which identifies the recipient).
type
Always includes both VerifiableCredential (W3C VC) and OpenBadgeCredential (OB 3.0). Additional types may appear for specialized credentials.
issuer
The signing authority. Always a DID reference pointing at the tenant's verification method fragment:
id—did:web:learncoin.me#tenant-<tenant-id>. This DID is resolvable athttps://learncoin.me/.well-known/did.json— the DID document contains the public key the signature was produced with.type—Profile(Open Badges 3.0 issuer type).name— the tenant's display name. Rendered on the verification page. Off-chain field; changing this doesn't invalidate old credentials because the cryptographic signature is over canonicalized content, andissuer.nameis a hint, not a verification input.
issuanceDate and expirationDate
ISO 8601 UTC timestamps. expirationDate is optional — omit for non-expiring credentials.
credentialSubject
The recipient + the achievement.
id— the pseudonymous recipient identifier. Alwaysurn:uuid:<v4>. LearnCoin never anchors a legal name, email, or other PII as the subject ID. The off-chain mapping from this UUID to the recipient's real identity lives in LearnCoin's Supabase under tenant-scoped RLS, and is what gets deleted on GDPR erasure.type—AchievementSubject(OB 3.0).name— the recipient's display name. Off-chain field, redacted to "Redacted on recipient request" on erasure.
credentialSubject.achievement
The OB 3.0 Achievement object. This is what the credential is about.
id— URL identifying the achievement definition. Best practice:https://youruniversity.edu/credentials/<achievement-slug>. Doesn't have to resolve.type—Achievement.name— the title of the achievement.description— one or two sentences on what was learned/done.criteria.narrative— the requirements that were met. Free-form prose.alignment[]— optional but high-value. Each entry maps the achievement to an external skill framework.
credentialSubject.achievement.alignment[]
Each alignment entry:
targetFramework— one ofESCO,O*NET,ISCED-F, orCUSTOM. Case-sensitive.targetCode— framework-specific skill code.targetName— human-readable name of the target skill.targetUrl— optional, but recommended — resolves to the framework's canonical page for this skill.
Why alignment matters: EU employers increasingly expect credentials to be readable by their ATS (applicant tracking systems). An ESCO-aligned credential is automatically understood by EU public-sector hiring tooling.
proof
The cryptographic signature. This is what makes the credential verifiable.
type— alwaysMerkleProof2019. Defined by Blockcerts v3.created— when the signature was produced (batch anchoring time).proofPurpose—assertionMethod. Distinguishes credential signatures from authentication signatures.verificationMethod— the exact key the signature was produced with, as a DID URL fragment. Must resolve inside the issuer's DID document.proofValue— the actual signature + Merkle path, base58-encoded. Verifiers decode this to get the leaf hash, the sibling hashes up to the root, and the ECDSA signature over the root.
The proofValue encodes:
- The SHA-256 hash of this credential's canonicalized form (the leaf).
- The Merkle path of sibling hashes from the leaf up to the anchored root.
- The issuer's ECDSA signature over the root.
- The Base transaction ID that committed the root.
Canonicalization — why the document must be exactly as-issued
Signature verification hashes the credential after JSON-LD canonicalization (URDNA2015). Canonicalization produces a deterministic byte string from the JSON-LD, regardless of key order or whitespace. That means:
- Pretty-printing the JSON does not break verification.
- Key reordering does not break verification.
- Whitespace changes do not break verification.
- Changing a value (even by a single character) does break verification — that's the point.
If a verifier fails with "signature mismatch" on a credential that looks fine, 99% of the time someone modified the document after it was signed.
Status fields (not in the signed document)
Revocation and erasure status are not part of the signed JSON-LD — that would require re-signing, which is impossible for immutable credentials. Instead, they're discoverable via:
- The
/c/{credential_id}public verification page (shows the current status) - The
GET /v1/credentials/{id}API response (returnsstatus,revoked,erasedfields alongside the signed document) - The
credentialStatusfield referenced via StatusList2021 / BitstringStatusList — on the roadmap, not shipped yet
See also
- /docs/concepts/verification — how a third party verifies this document end-to-end
- /docs/api/endpoints#get-v1credentialsid — API shape for fetching
- /standards — public standards page with audit evidence for every
@contextURL