Build Your Own Implementation
This page is for developers who want to build a Universal Manifest implementation in any programming language. The specification, conformance requirements, and test fixtures are all language-neutral. You do not need TypeScript, Node.js, or any specific runtime.
For an end-to-end conformance flow (decision tree, issuer/verifier path, and report submission), use the Implementation Guide and keep the Quick Reference open while implementing. If you are upgrading existing deployments, follow the Migration Guide: v0.1 to v0.2.
What you are building
Section titled “What you are building”A Universal Manifest implementation is a module that can:
- Parse a JSON-LD manifest document
- Validate its structure against required fields
- Enforce the validity window (TTL)
- Handle unknown fields safely (ignore, do not reject)
- (v0.2) Canonicalize using JCS and verify an Ed25519 signature
There are two conformance targets:
- Consumer (Verifier) — receives manifests and uses them to drive a surface (display, app, admin panel, etc.)
- Issuer (Signer) — produces manifests for a subject and delivers them to consumers
Most implementations start as consumers. You can add issuer capability later.
v0.1 implementation checklist (consumer)
Section titled “v0.1 implementation checklist (consumer)”This is the minimum viable implementation for a conformant v0.1 consumer.
Step 1: Parse the JSON document
Section titled “Step 1: Parse the JSON document”Accept a JSON string or object. Universal Manifests are valid JSON-LD, but you do not need a JSON-LD processor — a standard JSON parser is sufficient.
Step 2: Validate required fields
Section titled “Step 2: Validate required fields”Confirm all seven required fields are present and non-empty:
| Field | Type | Description |
|---|---|---|
@context | string | Must be a valid JSON-LD context URL |
@type | string or array | Must include "UniversalManifest" (check both string and array forms) |
@id | string (URI) | The UMID — a globally unique manifest identifier |
manifestVersion | string | The spec version (e.g., "0.1.0") |
subject | string (URI) | Who or what the manifest is about |
issuedAt | string (ISO 8601) | When the manifest was created |
expiresAt | string (ISO 8601) | When the manifest expires |
Step 3: Validate timestamps
Section titled “Step 3: Validate timestamps”- Parse
issuedAtandexpiresAtas ISO 8601 / RFC 3339 date-time strings (with timezone). - MUST reject if either timestamp is not a valid ISO 8601 date-time.
- RECOMMENDED: reject if
issuedAt > expiresAt.
Step 4: Enforce TTL (time-to-live)
Section titled “Step 4: Enforce TTL (time-to-live)”- MUST reject for use if
now > expiresAt. An expired manifest must not be used to grant permissions or render state. - You MAY retain expired manifests for debugging, but you MUST treat them as expired.
- SHOULD sanity-check that
issuedAtis not unreasonably far in the future (clock skew protection).
Step 5: Handle unknown fields
Section titled “Step 5: Handle unknown fields”- MUST ignore unknown top-level properties.
- MUST ignore unknown shard entity shapes.
- MUST ignore unknown items in
claims,consents,devices, andpointers.
This is the forward-compatibility rule. It ensures older implementations do not break when new fields are added in future versions.
Step 6: Test against conformance fixtures
Section titled “Step 6: Test against conformance fixtures”Run your implementation against the official fixture set. Your implementation MUST accept all valid fixtures and reject all invalid fixtures.
Valid fixtures (must accept):
- Minimal manifest
- Type-array manifest
- Unknown-fields manifest
- Manifest with shards
- Venue edge stub
- Display device stub
- Creator public capsule stub
- Social profile stub
- Display envelope stub
Invalid fixtures (must reject):
- Missing context
- Missing ID
- Empty ID
- Wrong type
- Missing subject
- Issued after expires
- Invalid issuedAt format
- Invalid expiresAt format
- Expired for use
- Shards not array
- Shard wrong type
v0.2 implementation checklist (consumer/verifier)
Section titled “v0.2 implementation checklist (consumer/verifier)”v0.2 adds cryptographic integrity verification on top of v0.1. A v0.2 consumer must do everything a v0.1 consumer does, plus signature verification.
Additional steps for v0.2
Section titled “Additional steps for v0.2”Verify the signature profile
Section titled “Verify the signature profile”- Confirm
signature.algorithm === "Ed25519" - Confirm
signature.canonicalization === "JCS-RFC8785" - MUST reject unsupported profile pairs when strict v0.2 verification is required.
Compute the signing input
Section titled “Compute the signing input”- Remove the
signatureproperty from the manifest object. - Canonicalize the remaining object using JCS (RFC 8785) — JSON Canonicalization Scheme. JCS produces a deterministic byte-level representation of any JSON value. Libraries are available for most languages (see the resources section below).
- The canonicalized bytes are the signing input.
Verify the Ed25519 signature
Section titled “Verify the Ed25519 signature”- Obtain the public key from
signature.publicKeySpkiB64(base64-encoded SPKI format) or by resolvingsignature.keyRef. - Decode the
signature.valuefrom base64url. - Verify the Ed25519 signature over the canonical bytes using the public key.
- MUST reject the manifest if verification fails. You may retain it for debugging.
v0.2 conformance fixtures
Section titled “v0.2 conformance fixtures”Valid fixtures (must accept):
Invalid fixtures (must reject):
- Missing signature
- Invalid signature
- Invalid signature algorithm
- Invalid signature canonicalization
- Invalid signature created format
- Missing signature key material
- Invalid signature public key
- Issued after expires (signed)
Issuer implementation checklist
Section titled “Issuer implementation checklist”If your system needs to produce manifests (not just consume them), implement the following:
v0.1 issuer requirements
Section titled “v0.1 issuer requirements”- Set
@idto a globally unique URI. Recommended:urn:uuid:<uuidv4>. - Set
subjectto a stable identifier URI. Recommended: a DID, but any URI works. - Set
@contextto the published JSON-LD context URL. - Set
@typeto"UniversalManifest"(string) or include"UniversalManifest"in an array. - Use short TTLs appropriate to your use case (hours or days, not months).
- Populate
shards,pointers,claims,consents, anddevicesas needed for your domain.
v0.2 issuer requirements (adding signatures)
Section titled “v0.2 issuer requirements (adding signatures)”- Produce a manifest with
manifestVersion: "0.2"and all required fields. - Remove the
signatureproperty from the object (if present from a previous version). - Canonicalize using JCS (RFC 8785).
- Sign with Ed25519.
- Embed the signature object with:
algorithm,canonicalization,keyReforpublicKeySpkiB64,created, andvalue.
Spec artifacts and resources
Section titled “Spec artifacts and resources”These are the language-neutral artifacts you need for any implementation:
Specification documents
Section titled “Specification documents”- Specification v0.1 — the normative contract for v0.1
- Specification v0.2 — the normative contract for v0.2 (adds signature profile)
Schemas and context files
Section titled “Schemas and context files”- JSON Schema (v0.1) — structural validation schema
- JSON-LD Context (v0.1) — semantic vocabulary definition
- Auto-discovery config —
.well-knownendpoint per RFC 8615
Conformance documents
Section titled “Conformance documents”- Conformance v0.1 — what a v0.1 consumer or issuer must do
- Conformance v0.2 — what a v0.2 consumer or issuer must do
Test fixtures
Section titled “Test fixtures”All fixtures are available as standalone JSON-LD files that you can download and use directly in your test suite:
- Valid v0.1 fixtures:
examples/v0.1/andexamples/v0.1/stubs/ - Invalid v0.1 fixtures:
examples/v0.1/invalid/ - Valid v0.2 fixtures:
examples/v0.2/ - Invalid v0.2 fixtures:
examples/v0.2/invalid/
These are also served on the site under /harness/fixtures/ — see the fixture links in the checklists above.
Libraries you will need
Section titled “Libraries you will need”These are the external capabilities your implementation needs, by version:
For v0.1
Section titled “For v0.1”- JSON parser — built into every modern language
- ISO 8601 date-time parser — most standard libraries include this
- UUID generator (issuer only) — for generating
@idvalues
For v0.2 (in addition to v0.1)
Section titled “For v0.2 (in addition to v0.1)”- JCS (RFC 8785) canonicalization — deterministic JSON serialization. Available as libraries in many languages:
- Python:
json-canonicalization - Go:
github.com/nicholasgrigoriadis/jcsorgithub.com/nicholasgrigoriadis/universal-manifest - Rust:
json-canonicalization - Java:
org.erdtman:jcs - JavaScript:
canonicalize(npm) - C#: various implementations available
- Python:
- Ed25519 signature library — widely available:
- Python:
cryptographyorpynacl - Go:
crypto/ed25519(stdlib) - Rust:
ed25519-dalek - Java:
java.security(JDK 15+) or Bouncy Castle - C#:
NSecorlibsodium-net
- Python:
Reference implementation
Section titled “Reference implementation”The TypeScript helper (universal-manifest npm package) is a working reference you can study, but it is not required. It demonstrates one way to implement the validation logic. The full source is a single file that you can read to understand the approach.
When building your own implementation, use the TypeScript helper as a reference for behavior, not as a dependency. Your implementation should pass the same conformance fixtures independently.
Implementation pseudocode
Section titled “Implementation pseudocode”Here is a language-neutral pseudocode sketch for a minimal v0.1 consumer:
function validateManifestV01(json): // Step 1: Parse manifest = parseJson(json) if manifest is not an object: reject("not a JSON object")
// Step 2: Required fields for field in ["@context", "@type", "@id", "manifestVersion", "subject", "issuedAt", "expiresAt"]: if field not in manifest or manifest[field] is empty: reject("missing required field: " + field)
// Step 3: Type check type = manifest["@type"] if type is a string: if type != "UniversalManifest": reject("@type must include UniversalManifest") else if type is an array: if "UniversalManifest" not in type: reject("@type must include UniversalManifest") else: reject("@type must be string or array")
// Step 4: Timestamp validation issuedAt = parseIso8601(manifest["issuedAt"]) expiresAt = parseIso8601(manifest["expiresAt"]) if issuedAt or expiresAt failed to parse: reject("invalid timestamp format") if issuedAt > expiresAt: reject("issuedAt must not be after expiresAt")
// Step 5: TTL enforcement if now() > expiresAt: reject("manifest has expired")
// Step 6: Unknown fields -- do nothing, just ignore them return manifest // validTips for implementers
Section titled “Tips for implementers”- Start with v0.1. Get the basic parse-validate-TTL flow working first. Add v0.2 signature verification later.
- Use the fixtures as your test suite. The conformance fixtures are the ground truth. If your implementation accepts all valid fixtures and rejects all invalid ones, it is conformant.
- Do not hardcode field lists. The unknown-field tolerance rule means your parser must not fail on unexpected properties. Use allowlists for the fields you read, and ignore everything else.
- Test the
@typearray form. Some manifests use"@type": "UniversalManifest"(string) and others use"@type": ["UniversalManifest"](array). Your implementation must handle both. - Keep TTL enforcement strict. Expired manifests must be rejected for use. This is a MUST requirement, not a recommendation.
- Quick Start — the step-by-step getting started guide
- Implementation Guide — full implementation-to-conformance path
- Quick Reference — one-page required-fields and signature checklist
- Migration Guide: v0.1 to v0.2 — compatibility matrix and upgrade steps
- Concepts — the five core ideas behind Universal Manifest
- How to Adopt Universal Manifest — adoption tiers and decision tree
- Conformance v0.1 — the full v0.1 conformance requirements
- Conformance v0.2 — the full v0.2 conformance requirements
- TypeScript Helper — the reference implementation (for studying, not required)