didwebvh-ts provides developers with a comprehensive library for working with Decentralized Identifiers (DIDs) following the did:webvh method specification. This TypeScript-based toolkit is designed to facilitate the integration and management of DIDs within web applications, enabling secure identity verification and authentication processes. It includes functions for creating, resolving, updating and deactivating DIDs by managing DID documents. The package is built to ensure compatibility with the latest web development standards, offering a straightforward API that makes it easy to implement DID-based features in a variety of projects.
The didwebvh-ts implementation of the did:webvh specification aims to be compatible with the did:webvh v1.0 specification.
Version 3.0.0 is a major release with breaking changes. Before upgrading, see:
- Migration Guide — Step-by-step upgrade instructions for each breaking change
- Breaking Changes Reference — Summary and rationale for all breaking changes
The examples directory contains sample code demonstrating how to use the library:
- Resolver Examples: The
examplesdirectory includes two resolver implementations:elysia-resolver.ts: (pnpm example:resolver) A resolver built with the Elysia web frameworkexpress-resolver.ts: A resolver built with Express.js Both examples demonstrate how to implement a DID resolver with different web frameworks. See the Examples README for more information.
- Signer Example: The
examples/signer.ts(pnpm example:signer) file demonstrates how to implement a custom signer usingAbstractCrypto.
This project uses:
- Node.js (runtime and package execution)
- TypeScript 7 (type-checking and language features)
- Vitest (test runner)
- pnpm (package management and scripts)
Install the following:
- Node.js 24+
- pnpm (via Corepack or npm)
With Corepack (recommended):
corepack enable
corepack prepare pnpm@latest --activatepnpm installBuild once before running local examples from source:
pnpm buildThen start the resolver example:
pnpm serverIf you need to refresh generated artifacts after code changes, rerun pnpm build.
The following commands are defined in the package.json file:
dev: Run the Elysia resolver example in watch mode for development.
pnpm devThis command runs: tsx --watch ./examples/elysia-resolver.ts and starts the resolver at http://localhost:3010 by default. Set PORT to use a different port.
debug: Run the Elysia resolver example in watch mode with Node inspector enabled.
pnpm debugThis command runs: tsx --watch --inspect ./examples/elysia-resolver.ts. Use the printed inspector URL for debugger tooling; the resolver still runs at the configured app port, defaulting to http://localhost:3010.
server: Alias for running the Elysia resolver example in watch mode.
pnpm serverThis command runs: tsx --watch ./examples/elysia-resolver.ts
test: Run all tests.
pnpm testThis command runs Vitest in non-watch mode.
test:watch: Run tests in watch mode.
pnpm test:watchtest:bail: Run tests in watch mode with bail and verbose options.
pnpm test:bailtest:log: Run tests and save logs to a file.
pnpm test:logcli: Run the CLI tool.
pnpm cliThe CLI accepts a --watcher option during create and update operations to specify one or more watcher URLs.
build: Build the package.
pnpm buildbuild:clean: Clean the build directory.
pnpm build:cleancheck: Run TypeScript 7 type-checking without emitting files.
pnpm checkPublishing is fully automated and happens only when a maintainer publishes a GitHub Release.
- Who can publish: GitHub users with write, maintain, or admin permission on this repo.
- Required tag format:
vMAJOR.MINOR.PATCH(for examplev2.7.5). - Required semver bump: the tag must be a single major/minor/patch increment over the latest existing
v*tag.
- In GitHub, go to Releases → Draft a new release
- Set Tag to the next version, e.g.
v2.7.5 - Choose the target branch/commit (typically
main) - Click Publish release
That will trigger the publish workflow, which will:
- validate the tag + your repo permission
- set
package.jsonversion from the tag (without the leadingv) - run
pnpm testandpnpm build - publish to npm
Publishing uses npm OIDC trusted publishing — the workflow exchanges its GitHub Actions OIDC token for a short-lived npm publish token at publish time. No static NPM_TOKEN is required.
For this to work, the didwebvh-ts package on npmjs.com must have a Trusted Publisher configured pointing at this repository and the .github/workflows/publish.yml workflow.
- Tag rejected: make sure it matches
vX.Y.Zand is exactly one major/minor/patch bump over the latestv*tag. - Permission rejected: ensure the releasing user has write/maintain/admin permission on the GitHub repo.
EOTP/ OTP required at publish: the npm token path is being used instead of OIDC. Make sure noNODE_AUTH_TOKENis set on the publish step and that the workflow hasid-token: writepermission.- OIDC exchange failed: confirm the Trusted Publisher config on npmjs.com matches this repo's owner, name, and workflow file path (
.github/workflows/publish.yml).
Resolution follows the standard W3C did-resolver interface. resolveDID / resolveDIDFromLog return a DIDResolutionResult ({ didResolutionMetadata, didDocument, didDocumentMetadata }), and getResolver() produces a registry entry you can drop into a did-resolver Resolver alongside did:web, did:ethr, etc.
import { Resolver } from 'did-resolver';
import { getResolver } from 'didwebvh-ts';
// Works zero-config via the built-in Ed25519 verifier;
// pass getResolver({ verifier }) to override.
const resolver = new Resolver(getResolver());
const result = await resolver.resolve('did:webvh:SCID:example.com');
// Spec-conformant query parameters are honoured:
const v2 = await resolver.resolve('did:webvh:SCID:example.com?versionId=2-...');versionId, versionTime, and versionNumber are mutually exclusive — supplying more than one returns didResolutionMetadata.error = "invalidOptions" with a problemDetails.type from the did:webvh resolution-error registry.
import { resolveDID } from 'didwebvh-ts';
// Example using Express
app.get('/resolve/:id', async (req, res) => {
const result = await resolveDID(req.params.id);
res.json(result);
});resolveDID does not throw on failure — it returns a DIDResolutionResult with didDocument: null and a didResolutionMetadata.error code.
For complete examples, see the examples directory.
Resolver failures are surfaced on didResolutionMetadata:
didResolutionMetadata.erroris one of"invalidDid"(the resolved DID or log fails validation),"invalidDidUrl"(the DID URL violatesdid-urlsyntax, e.g. malformed percent-encoding),"invalidOptions"(conflicting or ill-typed version selectors),"notFound", or"internalError"(transport/resolver-side failure). Unknown query parameters are ignored per DID Core extensibility.didResolutionMetadata.problemDetailscarries RFC9457-style fields (type,title,detail) where available, anddidResolutionMetadata.messagecarries the underlying detail string.- Whether the resolved DID is locally controlled rides along as
didResolutionMetadata.controlled(a non-standard extension).
Absence cases (missing DID log or missing DID URL resource) use didResolutionMetadata.error = "notFound".
When resolving a requested earlier version (with versionId, versionNumber, or versionTime), the resolver may return a valid earlier document while still reporting didResolutionMetadata.error = "invalidDid" if a later log entry fails verification.
Method-specific metadata (scid, updateKeys, nextKeyHashes, prerotation, portable, witness, watchers, previousLogEntryHash, latestVersionId) is returned on didDocumentMetadata alongside the standard versionId/created/updated/deactivated fields.
Breaking change (v3.0.0): resolution returns the standard
DIDResolutionResultinstead of the previous{ did, doc, meta, controlled }shape, and the implementation-specificverificationMethodresolution selector has been removed. See Migration Guide for upgrade steps.
-
getResolver(config?: { verifier?: Verifier }): ResolverRegistryReturns adid-resolverregistry entry ({ webvh: DIDResolver }) registrable in aResolver. Works zero-config viadefaultVerifier. -
defaultVerifier: VerifierBuilt-in Ed25519 verifier used when noverifieris supplied. -
resolveDID(did: string, options?: ResolutionOptions): Promise<DIDResolutionResult>Resolves a DID to a standard W3CDIDResolutionResult({ didResolutionMetadata, didDocument, didDocumentMetadata }). Does not throw on failure. -
resolveDIDFromLog(log: DIDLog, options?: ResolutionOptions & { witnessProofs?: WitnessProofFileEntry[] }): Promise<DIDResolutionResult>Resolves directly from an in-memory DID log, returning the same standard shape. -
createDID(options: CreateDIDInterface): Promise<{did: string, doc: any, meta: DIDResolutionMeta, log: DIDLog, webDoc?: DIDDoc}>Creates a new DID. Acceptsaddress(host,host:port,https://..., ordid:webvh:...) or legacydomain. Resolver URL mapping useshttp://localhostfor local testing andhttps://for non-local hosts. IfalsoKnownAsWeb: trueis supplied, the result also includeswebDoc, the paralleldid:webDID document to publish asdid.json. -
updateDID(options: UpdateDIDInterface): Promise<{did: string, doc: any, meta: DIDResolutionMeta, log: DIDLog, webDoc?: DIDDoc}>Updates an existing DID. ReturnswebDocwhen the updated DID document carries adid:web:alias inalsoKnownAs. -
deactivateDID(options: DeactivateDIDInterface): Promise<{did: string, doc: any, meta: DIDResolutionMeta, log: DIDLog}>Deactivates an existing DID. -
generateParallelDidWeb(didwebvhDid: string, didwebvhDoc: DIDDoc): DIDDocGenerates the paralleldid:webdocument defined by did:webvh v1.0 §3.7.10.
-
createWitnessProof(signer, versionId, verificationMethod, created?): Promise<DataIntegrityProof>Creates and signs one witness proof for a specificversionId. -
signWitnessProofEntry(options: WitnessSigningOptions): Promise<WitnessSigningResult>Signs one did-witness proof entry ({ versionId, proof[] }) for a single target version. -
signWitnessProofEntries(versionIds: string[], witnesses: WitnessEntry[], witnessSignersByDid: Record<string, WitnessSigner>, created?: string): Promise<WitnessSigningResult[]>Signs did-witness proof entries for multiple target versions.
-
createDocumentSigner(options: SignerOptions): SignerCreates a signer for signing DID documents. -
prepareDataForSigning(data: any): Uint8ArrayPrepares data for signing. -
createProof(options: SigningInput): Promise<SigningOutput>Creates a proof for a DID document. -
createSigner(options: SignerOptions): SignerCreates a signer for signing data. -
AbstractCryptoAn abstract class for implementing custom signers.
This project is licensed under the MIT License.