Verification and sharing
Once an issuance completes, a signed credential (VC-JWT) is created shortly afterwards. This guide covers how to find that credential, how to verify it, and how to share it with the recipient. You never implement verification yourself: the verdict comes from Kolleges’ public verification endpoint, and the API hands you pointers to it.
Issuance and credential are two different moments
Section titled “Issuance and credential are two different moments”When POST /v2/clubs/{clubDomain}/issuances returns 200, the issuance record exists, but the signed credential is baked in the background a moment later. Requesting the credential in between returns 409 credential-pending. Wait for the event instead of polling.
-
Issue.
results[].resourceIdin the response is the issuance id. -
Wait for
issuance.credential_issued. When that event appears in theGET /v2/clubs/{clubDomain}/eventsfeed, the signed credential exists.subject.idis the issuance id. -
Read the credential.
GET /v2/clubs/{clubDomain}/issuances/{issuanceId}/credentialreturns{ id, format, jwt, payload }.jwtis the compact JWT,payloadits decoded claims.
// GET /v2/clubs/acme/events{ "data": [ { "id": "…", "type": "issuance.credential_issued", "occurredAt": "2026-09-03T06:12:41.000Z", "subject": { "type": "issuance", "id": "1450" } }, { "id": "…", "type": "issuance.issued", "occurredAt": "2026-09-03T06:12:38.000Z", "subject": { "type": "issuance", "id": "1450" } } ], "meta": { "nextCursor": null }}The credential block on the issuance detail
Section titled “The credential block on the issuance detail”credential on GET /v2/clubs/{clubDomain}/issuances/{issuanceId} is a pointer to the credential. It is null until the credential has been baked.
| Field | Meaning |
|---|---|
id |
The credential UUID, identical to the id inside the credential |
format |
OB3_VC_JWT |
status |
valid, expired or revoked; revocation wins over expiry. The signature is not checked here |
issuedAt |
When the credential was created |
shareUrl |
The share and verification page to send to the recipient (https://{clubDomain}.kolleges.net/credentials/{id}) |
verifyUrl |
The public verification API, returning a JSON verdict without authentication |
jwtUrl |
The public JWT endpoint, returning the signed JWT (text/plain) without authentication |
List items carry achievementId and credentialId. A null credentialId means the credential has not been baked yet.
Unauthenticated public verification
Section titled “Unauthenticated public verification”Verifiers, wallets and LMSs that hold no partner key call the Open Badges 3.0 public surface directly. The reference is at Open Badges 3.0 · Verification.
| Endpoint | What it returns |
|---|---|
GET https://api.kolleges.net/open-badges/v3/achievements/{issuanceId}/verify |
isValid (signature verdict), status, issuerVerified (issuer trust), payload (decoded VC) |
GET https://api.kolleges.net/open-badges/v3/credentials/{credentialId} |
The signed JWT (text/plain) |
GET https://api.kolleges.net/open-badges/v3/issuers/{domain}/did.json |
The issuer’s DID Document (public keys) |
GET https://api.kolleges.net/open-badges/v3/issuers/{domain}/status-lists/revocation |
The revocation status list (Bitstring Status List) |
payload.vc.credentialSubject.achievement in the verify response carries the criteria narrative, the skills (tag, alignment) and the engraved description exactly as they were at issuance. That is the frozen record described in Competency data.
// GET https://api.kolleges.net/open-badges/v3/achievements/1450/verify{ "statusCode": 200, "isValid": true, "status": "valid", "issuerVerified": true, "payload": { "iss": "did:web:api.kolleges.net:open-badges:v3:issuers:acme", "vc": { "credentialSubject": { "achievement": { "achievementType": "CertificateOfCompletion", "criteria": { "narrative": "1. Complete the 12-week curriculum\n2. Present the final project (https://acme.example/demo-day)" }, "tag": ["React", "TypeScript", "Teamwork"] } } } }}isValid is a verdict about the credential; issuerVerified is a commercial trust attestation about the issuing organisation. They are independent axes, so a signature can be valid while issuerVerified is false.
Sharing with the recipient
Section titled “Sharing with the recipient”Send the recipient the shareUrl. That page opens without authentication, shows the credential’s current verdict, and is the entry point for the recipient to take the credential into a wallet or onto social media. A revoked credential shows as revoked there too.
If you are on v1 (/v1)
Section titled “If you are on v1 (/v1)”credential { id, shareUrl, verifyUrl } | null on GET /v1/achievements/{id} is the same pointer. You can read it back immediately using issued[].id from the bulk-issue response. The signed JWT itself comes from the public credentials/{credentialId} endpoint above.