Verifiable Credentials
Verak issues W3C Verifiable Credentials to verified members. A VC is a signed structured achievement record — distinct from a label, which is a binary trust signal. Labels tell you whether someone is verified; VCs tell you what was verified, when, by whom, and with what proof.
This page documents the VC stack, where credentials live, and how to verify them independently using standard libraries with no Verak code.
VC Stack
| Layer | Standard |
|---|
| Credential data model | W3C VC Data Model 2.0 |
| Achievement type | Open Badges 3.0 (OpenBadgeCredential) |
| Proof suite | Ed25519Signature2020 |
| Issuer identity | did:web:labeler.verak.app |
| Subject identity | Member’s did:plc |
| Storage | Member’s AT Protocol PDS (is.verak.passport.credential) |
Verak does not store issued VCs on its own infrastructure. After issuance, the signed credential is delivered to the member’s PDS. Verak retains a public viewer record (at /credentials/{id}) for human-readable display, but the canonical credential lives with the member.
Issuer Resolution
The issuer is identified by did:web:labeler.verak.app. To resolve the issuer’s DID document:
curl "https://labeler.verak.app/.well-known/did.json"
The DID document is served with CORS headers (Access-Control-Allow-Origin: *), which means browser-based VC wallets can resolve it directly without a proxy.
The document contains a verificationMethod entry for the issuer’s Ed25519 signing key:
{
"@context": ["https://www.w3.org/ns/did/v1"],
"id": "did:web:labeler.verak.app",
"verificationMethod": [
{
"id": "did:web:labeler.verak.app#key-1",
"type": "Ed25519VerificationKey2020",
"controller": "did:web:labeler.verak.app",
"publicKeyMultibase": "z6Mk..."
}
],
"assertionMethod": ["did:web:labeler.verak.app#key-1"]
}
The publicKeyMultibase value is the base58btc-encoded public key used to verify all Verak-issued credentials.
Subject Convention
All Verak VCs use the member’s did:plc as the credential subject identifier:
{
"credentialSubject": {
"id": "did:plc:member-did-here",
"type": "AchievementSubject",
"achievement": { ... }
}
}
This means you can verify that a credential was issued to a specific AT Protocol identity without knowing the member’s handle, email, or any other personal identifier.
Locating Credentials on a Member’s PDS
Credential references are stored as is.verak.passport.credential records on the member’s PDS. Each record is a reference — it contains the credential metadata and a URI pointing to the full signed VC.
Step 1 — Resolve the member’s PDS endpoint
curl "https://plc.directory/did:plc:MEMBER-DID"
Locate the entry in the service array where type is AtpPersonalDataServer. The serviceEndpoint value is the PDS host.
Step 2 — List credential records
curl "{pds-host}/xrpc/com.atproto.repo.listRecords
?repo=did:plc:MEMBER-DID
&collection=is.verak.passport.credential
&limit=50"
Public records (those with isPublic: true) are returned without authentication. The full list, including private records, is only accessible to the account owner via an authenticated session.
Step 3 — Fetch the signed VC
Each record contains a credentialUri field pointing to the full JSON-LD credential. For Verak-issued VCs, this resolves to:
curl "https://verak.app/api/credentials/{id}"
The response is the raw JSON-LD signed VC with Content-Type: application/ld+json.
Worked Example: End-to-End Verification
The following example fetches a credential reference from a member’s PDS, resolves the full VC, and verifies the Ed25519 proof using the @digitalbazaar/vc family of libraries. No Verak code is required.
import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020'
import { Ed25519Signature2020 } from '@digitalbazaar/ed25519-signature-2020'
import * as vc from '@digitalbazaar/vc'
import jsonld from 'jsonld'
// 1. Resolve the issuer's DID document and extract the signing key
const didDoc = await fetch('https://labeler.verak.app/.well-known/did.json')
.then(r => r.json())
const verificationMethod = didDoc.verificationMethod.find(
vm => vm.type === 'Ed25519VerificationKey2020'
)
const issuerKey = await Ed25519VerificationKey2020.from(verificationMethod)
// 2. Build a document loader that resolves the issuer DID locally
// (avoids a network round trip during verification)
const documentLoader = jsonld.documentLoaders.node()
const customLoader = async (url) => {
if (url === 'did:web:labeler.verak.app' || url.startsWith('did:web:labeler.verak.app#')) {
return { contextUrl: null, document: didDoc, documentUrl: url }
}
return documentLoader(url)
}
// 3. Fetch the signed credential
const credential = await fetch('https://verak.app/api/credentials/CREDENTIAL-ID')
.then(r => r.json())
// 4. Verify
const suite = new Ed25519Signature2020({ key: issuerKey })
const result = await vc.verifyCredential({
credential,
suite,
documentLoader: customLoader,
})
if (result.verified) {
const subject = credential.credentialSubject.id // did:plc:...
const achievement = credential.credentialSubject.achievement.name
console.log(`${achievement} verified for ${subject}`)
} else {
console.error('Verification failed:', result.error)
}
What a successful result means
- The credential was issued by
did:web:labeler.verak.app
- The
credentialSubject.id is the member’s AT Protocol DID
- The proof has not been tampered with since issuance
- The credential has not expired (check
expirationDate if present)
What it does not mean
Proof verification confirms cryptographic integrity. It does not confirm that the underlying evidence reviewed by Verak editors is still current. For live trust state — including revocation — query the labeler directly.
Credential Structure Reference
A Verak-issued VC has this shape:
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "https://verak.app/api/credentials/CREDENTIAL-ID",
"type": ["VerifiableCredential", "OpenBadgeCredential"],
"issuer": {
"id": "did:web:labeler.verak.app",
"type": "Profile",
"name": "Verak"
},
"issuanceDate": "2026-06-01T00:00:00.000Z",
"credentialSubject": {
"id": "did:plc:member-did-here",
"type": "AchievementSubject",
"achievement": {
"id": "https://verak.app/achievements/verified-professional",
"type": ["Achievement"],
"name": "Verified Professional",
"description": "Identity and professional standing independently reviewed and verified by Verak editors.",
"criteria": {
"narrative": "Issued after a Verak editor reviews submitted evidence signals and confirms professional identity."
}
}
},
"proof": {
"type": "Ed25519Signature2020",
"created": "2026-06-01T00:00:00.000Z",
"verificationMethod": "did:web:labeler.verak.app#key-1",
"proofPurpose": "assertionMethod",
"proofValue": "z..."
}
}
Dependencies
The worked example above requires:
npm install @digitalbazaar/vc \
@digitalbazaar/ed25519-verification-key-2020 \
@digitalbazaar/ed25519-signature-2020 \
jsonld
These are maintained by the Digital Bazaar team and implement the W3C VC Data Model and Ed25519 proof suite specifications. No Verak packages are required.
These libraries use the Node.js runtime. The jsonld package uses a document loader that requires network access at verify time for any @context URLs it hasn’t already resolved. The custom document loader in the example above short-circuits the issuer DID resolution to avoid the network round trip. You may want to cache the W3C and IMS contexts locally for production use.