GlemoDocs
Concepts

Verification

A verdict, and the separate facts behind it, cross-issuer.

Verification is Glemo's product. One call returns a deterministic verdict with the checks that produced it.

Verdicts

VerdictMeaning
validSignature, schema, status and validity window all pass
revokedThe issuer flipped this credential's bit in their status list
expiredPast validUntil
tamperedSignature or schema check failed, the content was altered
not_foundNo credential with that id
not_verifiableWe could not check it: the issuer's DID did not resolve (network, unknown issuer), or an uploaded image carries no credential we can read. Note the difference from not_found, which asserts the credential does not exist

The facts behind the verdict

The verdict is a summary. Alongside it every verification returns a report: the facts, one by one, in the shape the W3C VCDM 2.0 verification result defines. You do not have to accept our summary to use our answer.

FieldWhat it says
proofWhether the signature verified against the issuer's key
credentialStatusThe issuer's status list, as a list whose value is an integer, not a boolean: a credential can be suspended rather than revoked, and a boolean cannot say so
validFrom, validUntilTwo facts, not one. "Not valid yet" and "no longer valid" are different answers, and a single expired flag collapses them
provenanceHow the evidence reached us: artifact, zktls, wallet or registry. Two of the four work today, and every zkTLS recipe in the issuer registry is still sandbox: no real proof has been generated yet
observations.issuerTrustedWhether the issuer holds an accreditation
warnings, errorsRFC 9457 problem details, split by whether they are recoverable

Two rules make the report honest, and both are worth knowing before you build on it:

A fact we did not check comes back null with its reason, never false. If no trust registry is configured, issuerTrusted is not "the issuer is untrusted": it is "nobody asked". A verifier that renders those the same way is telling its user something we never said.

Errors and warnings are not the same kind of thing. A signature that does not verify is an error: nothing downstream can recover from it. A credential listed in its issuer's status list is a warning: it is a fact about status, and what to do about it is the calling application's decision, not ours.

Methods

  • byHash: look up a credential by id in the registry, check status and expiration. The fast path (sub-millisecond server compute).
  • byVC: verify a portable VC-JWT: resolve the issuer's DID, check the ES256 signature, the schema, the status list and the validity window. Works for credentials issued by any issuer, not just those registered in Glemo.

Cross-issuer

For external issuers, Glemo resolves did:web documents over HTTPS (with caching and timeouts) and fetches their status lists. If an issuer can't be resolved, the verdict is an explicit not_verifiable, never a hang, never a guess.

Webhooks

Every verification can notify your systems: see Webhooks.

Signed receipts

Every authenticated verification returns a signedEvidence field: a signed record of what you checked, what came back, and when. It is a Security Event Token (RFC 8417), so it carries no expiry: it describes something that already happened, and a receipt you produce in three years to show what you checked must not have gone stale.

Failed verdicts get a receipt too. Proving that you checked and it came back revoked is usually the reason you need one.

We keep no copy. The receipt is yours; if you lose it we can issue a new one, but its iat will be today and the difference is visible in the token.

Verifying one, without asking us

The receipt verifies against the published key set. This depends on jose and nothing of ours: no API key, no SDK, no call to a private endpoint.

import { createRemoteJWKSet, jwtVerify } from "jose";

const JWKS = createRemoteJWKSet(new URL("https://api.glemo.io/.well-known/jwks.json"));

const { payload, protectedHeader } = await jwtVerify(signedEvidence, JWKS, {
  issuer: "https://api.glemo.io",
});

console.log(protectedHeader.typ); // secevent+jwt
console.log(payload.toe, payload.txn); // when it happened, which verification
console.log(payload.events["https://glemo.io/ns/verification"]);

toe is when the verification ran and iat is when the receipt was signed: two instants, not one. txn correlates with the verification event in your own logs.

A receipt whose payload was altered fails with ERR_JWS_SIGNATURE_VERIFICATION_FAILED; one signed with a key that is not in the set fails with ERR_JWKS_NO_MATCHING_KEY. Rotating our key does not invalidate old receipts, because the superseded key stays published.

On this page