Affinidi WebVH Watcher
The WebVH Watcher is a read-only DID mirror that receives pushed DID updates from did-hosting-server instances and serves them publicly. It provides redundancy, geographic distribution, and load balancing for DID resolution without managing DIDs directly.
The watcher has no authentication, no ACL, and no DID lifecycle management — it simply stores and serves replicated DID content.
IMPORTANT: did-hosting-service crates are provided "as is" without any warranties or guarantees, and by using this framework, users agree to assume all risks associated with its deployment and use including implementing security, and privacy measures in their applications. Affinidi assumes no liability for any issues arising from the use or modification of the project.
Getting Started
Prerequisites
- Rust 1.94.0+ (2024 Edition)
1. Build
The binary is produced at target/release/webvh-watcher.
2. Configure
Create a config.toml:
[]
= "0.0.0.0"
= 8533
[]
= "info"
[]
= "data/webvh-watcher"
[]
# Shared secret tokens that source servers must present when pushing
= ["my-shared-secret-token"]
# Optional: source servers to pull from for reconciliation
# [[sync.sources]]
# url = "http://server1:8530"
# token = "pull-token"
3. Configure the source server
On the did-hosting-server that will push DID updates, add the watcher
to its config.toml:
[[]]
= "http://watcher1.example.com:8533"
= "my-shared-secret-token"
The token must match one of the watcher's sync.push_tokens.
4. Start the watcher
How It Works
did-hosting-server ──(POST /api/sync/did)──► webvh-watcher
──(POST /api/sync/delete)──►
Clients ──(GET /{mnemonic}/did.jsonl)──► webvh-watcher
- A DID is published on a did-hosting-server
- The server pushes the DID content to all registered watchers
- The watcher stores the content and serves it at the same public paths as the original server
- Clients can resolve DIDs from any watcher instance
Push failures on the server side are logged but do not block the primary publish operation.
Configuration
The watcher is configured via a TOML file. By default it looks
for config.toml in the current directory. You can specify a
different path with the --config flag or the
WATCHER_CONFIG_PATH environment variable.
Environment Variable Overrides
| Variable | Description |
|---|---|
WATCHER_CONFIG_PATH |
Path to config file |
WATCHER_SERVER_HOST |
Bind host |
WATCHER_SERVER_PORT |
Bind port |
WATCHER_LOG_LEVEL |
Log level |
CLI Commands
webvh-watcher # Run watcher (default)
webvh-watcher setup # Interactive config wizard
webvh-watcher setup --from <recipe.toml> # Non-interactive (see examples/)
webvh-watcher setup --from <recipe.toml> --force-reprovision # overwrite existing
The watcher has no VTA / no secret store, so the recipe is just
[deployment], [output], [server], and [watcher] — see
examples/webvh-watcher-build.toml.
API Endpoints
Public (unauthenticated)
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Health check |
GET |
/{mnemonic}/did.jsonl |
Resolve DID log |
GET |
/{mnemonic}/did-witness.json |
Resolve witness |
GET |
/.well-known/did.jsonl |
Root DID log |
GET |
/.well-known/did-witness.json |
Root witness |
Sync (token-authenticated)
| Method | Path | Description |
|---|---|---|
POST |
/api/sync/did |
Receive pushed DID |
POST |
/api/sync/delete |
Receive DID deletion |
Sync endpoints require a Bearer token matching one of the
configured sync.push_tokens.
Library Usage
The webvh-watcher crate can be used as a library (e.g., by the did-hosting-daemon). It exposes:
webvh_watcher::config::AppConfig— configurationwebvh_watcher::server::AppState— application statewebvh_watcher::routes::router()— Axum routerwebvh_watcher::server::run()— standalone entry point
Support & feedback
If you face any issues or have suggestions, please don't hesitate to contact us using this link.
Reporting technical issues
If you have a technical issue with the Affinidi DID Hosting Service codebase, you can also create an issue directly in GitHub.
-
Ensure the bug was not already reported by searching on GitHub under Issues.
-
If you're unable to find an open issue addressing the problem, open a new one. Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behaviour that is not occurring.
Contributing
Want to contribute? Head over to our CONTRIBUTING guidelines.