Project Summary โ Verifiably
verifiably-go ยท Status presentation ยท May 18, 2026
Executive Summary
We built a complete platform for issuing and verifying W3C Verifiable Credentials
(OID4VC) targeting digital government ecosystems. The platform scales from a
single standalone deployment to a federated ecosystem of N ministries with a
central Hub operating the trust infrastructure and a citizen-facing verification
portal โ no vendor lock-in, no proprietary wallet, privacy by design.
1. Starting point โ main baseline
The main codebase already had:
| Component | Status |
|---|---|
| DPG adapters | walt.id, Inji Certify, Inji Verify, Inji Web |
Unified backend.Adapter interface |
Complete โ swap vendors without touching the UI |
| OIDC sign-in | Keycloak + WSO2 IS |
| Translation | LibreTranslate (English / French / Spanish) |
| Deployment | deploy.sh โ interactive wizard, TLS subdomains via Caddy |
What did not exist: CREDEBL, Trust Registry, metrics, PostgreSQL persistence,
headless API, or any federation component.
2. Stage 1 โ CREDEBL Integration + Codebase Maturity (add-credebl)
CREDEBL adapter
- OID4VCI pre-auth issuance + OID4VP verification with DCQL
- Fully automated bootstrap: Keycloak realm, platform-admin, DID, issuer,
credential template โ zero manual steps - 18 microservices orchestrated with a single command:
./deploy.sh up credebl
Trust Registry (JWT ES256)
GET /trust-registryโ ES256-signed JWT with the list of trusted issuersGET /.well-known/jwks.jsonโ public key for external verification- Storage: PostgreSQL (+ in-memory fallback for dev)
- Upgrade path to OpenID Federation 1.0 with no interface changes
Headless REST API for system integration
Machine-to-machine API over Authorization: Bearer <key> for external
systems that need to issue or verify credentials without a browser session.
Configured via VERIFIABLY_API_KEYS env var (name1:key1,name2:key2).
Each key is owner-scoped โ credentials issued by key A are never visible to
key B. Rate limiting: 60 req/min per key. Interactive docs at /api/docs
(Scalar UI) and raw spec at /api/openapi.yaml.
| Endpoint | Role | Description |
|---|---|---|
POST /api/v1/credentials/issue |
issuer |
Issue one credential โ returns OID4VCI offer_uri |
POST /api/v1/credentials/issue/bulk |
issuer |
Issue N credentials synchronously |
POST /api/v1/credentials/issue/bulk/async |
issuer |
Submit async batch job โ HTTP 202, returns job_id immediately |
GET /api/v1/bulk/{jobID} |
issuer |
Poll async job status |
GET /api/v1/bulk/{jobID}/events |
issuer |
SSE stream of job progress |
GET /api/v1/credentials |
issuer |
List credentials issued under this API key |
GET /api/v1/credentials/{id} |
issuer |
Get one issuance record |
POST /api/v1/credentials/{id}/revoke |
issuer |
Revoke (flips the status list bit) |
POST /api/v1/credentials/{id}/reinstate |
issuer |
Un-revoke a credential |
POST /api/v1/verify/request |
verifier |
Create an OID4VP presentation request |
GET /api/v1/verify/result/{state} |
verifier |
Poll for verification result |
GET /api/ecosystem/issuers/{did}/stats |
Hub only | 30-day verification stats for an issuer |
Observability and quality
- Prometheus + Grafana: issuance/verification counters, per-adapter latency
histograms, 6 real-time metric panels - stdlib-only OpenTelemetry: W3C traceparent traces propagated to all adapters;
Slog (Loki) + OTLP JSON (Tempo/Jaeger) exporters - PostgreSQL backend: sessions (AES-GCM encrypted), issuance log (SHA-256
tamper-evident hash chain), status lists - Redis: distributed sessions for horizontal scaling with L7 sticky sessions
Security and reliability (P0โP3 review)
26 items resolved across security, bugs, and observability, classified into
four priority tiers. Critical items include:
- PII removed from the issuance log and the PostgreSQL backend
- CREDEBL credentials moved from
backends.jsonto environment variables - Race conditions fixed in sessions, the global translator, and
resolveTemplateID - Rate limiting per API key (60 req/min) + per IP with proxy allowlist
- Secure session mode:
Securecookie flag, validated redirect URI,/langopen redirect closed - Tamper-evident hash chain in the issuance log
3. Stage 2 โ Federated Ecosystem (federated-issuance)
Ecosystem architecture on running demo
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ HUB (credenciales.ysalabs.work) โโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Trust Registry โ ES256 JWT, public JWKS โ
โ Schema Registry โ aggregated + cached from all issuers โ
โ Portal /verify โ no login required, citizen-facing โ
โ Admin โ member CRUD, API keys, health dashboard โ
โ Grafana โ full ecosystem dashboard โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
Ministry of Education Ministry of Labour
ROLES=issuer ยท DPG: walt.id ROLES=issuer ยท DPG: CREDEBL
did:web:verifiably.minerd.ysalabs.work did:web:verifiably.mt.ysalabs.work
13 implemented phases
| # | Phase | Description |
|---|---|---|
| 0 | Baseline | CREDEBL, Trust Registry, Prometheus/Grafana, PG/Redis |
| 0.5 | did:web Inji | ISSUER_DID_DOMAIN enables did:web across the entire Inji stack automatically |
| 1 | Roles | VERIFIABLY_ROLES โ conditional module activation per instance |
| 1.5 | DID Resolver + ES256 | Generic did:web resolver with 10-min cache; JWT migration to ES256 |
| 4 | Federation Config | federation.json as seed; DB as master; state-prefix routing |
| 5 | Trust Registry CRUD | Extended with ServiceEndpoint; admin /admin/federation/members |
| 2 | Public Portal | /verify for citizens โ no login, schema picker + QR + trust badge |
| 10 | Status List Cache | Status list cache with ES256 signature verification and fail-closed policy |
| 3 | Schema Federation | Aggregator with 5-min TTL cache โ zero added latency on /verify |
| 6 | Events Log | verification_events in PostgreSQL โ no holder PII stored |
| 7 | Analytics API | GET /api/ecosystem/issuers/{did}/stats โ issuers pull their own stats |
| 8 | Prometheus Federation | Hub aggregates metrics from all members via file_sd hot-reload |
| 9 | Trust Health Monitor | Days-until-expiry + endpoint up/down gauges; 3 Prometheus alert rules |
Additional components built during production deployment
Discovered and resolved while deploying with MINERD and MT:
verifiablyadapter: lets the Hub delegate OID4VP verification to another
verifiably-go instance via API key โ without exposing the underlying DPG- Full OID4VP template in
/api/v1/verify/request: the Hub can send the complete
template (fields, format, disclosure) to the issuer's verification endpoint - Hub admin landing page: post-login entry screen for Hub operators
- fix:
VERIFIABLY_API_KEYSpassthrough โ env var was not reaching the Docker container - fix:
host.docker.internalon Linux โcredebl-oid4vci-rewriterwas resolving to
172.17.0.1(Docker bridge) which cannot reach the Credo agent on the compose network;
_credebl_configure_oid4vci_rewriternow detects the container's actual IP - fix: already-provisioned wallet recovery โ CREDEBL returns 409 on re-provision;
bootstrap now recovers the existing wallet instead of failing - i18n on the public portal โ
renderPublicPagenow runs the same translation
pipeline as the admin portal
4. Current running demo status
| Instance | URL | DPG | Status |
|---|---|---|---|
| Hub | credenciales.ysalabs.work |
โ | โ live |
| Hub Admin | admin.credenciales.ysalabs.work |
โ | โ live |
| MINERD | verifiably.minerd.credenciales.ysalabs.work |
walt.id | โ live |
| MT | verifiably.mt.credenciales.ysalabs.work |
CREDEBL | โ live |
End-to-end flow verified in production:
- Citizen visits
credenciales.ysalabs.work - Selects a document type (schemas from MINERD or MT)
- Scans the QR code with any OID4VC-compatible wallet
- Wallet fetches the authorization request JWT from the corresponding issuer's agent
- Citizen presents the credential
- Hub displays the badge: โ Verified โ with issuer, trust level, and status list source
5. Privacy and trust design
| Principle | Implementation |
|---|---|
| No holder PII | verification_events never writes citizen data |
| No verification correlation | The Hub cannot determine whether two verifications belong to the same holder |
| Status list privacy | Bitstring / Token Status List โ the issuer only knows someone fetched the list, not which credential was checked |
| Asymmetric signing | Trust Registry JWT signed ES256; public key at /.well-known/jwks.json |
| No vendor lock-in | Any OID4VC-compatible wallet works (tested with Inji Web and CREDEBL wallets) |
6. Hub infrastructure
# docker-compose.yml โ hub stack
hub-postgres: PostgreSQL 16 โ Trust Registry, events, API keys
verifiably-go: Hub app (VERIFIABLY_ROLES=hub)
hub-prometheus: Self-scrape + federation scrape of all members (file_sd)
hub-grafana: Ecosystem dashboard โ issuance, verification, health
Key Hub environment variables:
| Variable | Purpose |
|---|---|
VERIFIABLY_ROLES=hub |
Enables only hub modules (trust + schemas + portal) |
VERIFIABLY_TRUST_SIGNING_KEY |
ES256 PEM private key for signing the Trust Registry JWT |
VERIFIABLY_DATABASE_URL |
PostgreSQL DSN โ Trust Registry, events, API keys |
VERIFIABLY_PUBLIC_URL |
Hub public URL (embedded in the events log) |
VERIFIABLY_ADMIN_PASSWORD |
Hub admin password |
7. Configured monitoring alerts
| Alert | Condition | Severity |
|---|---|---|
IssuerAccreditationExpiringSoon |
days_until_expiry < 30 |
warning |
IssuerEndpointDown |
/healthz endpoint down > 10 min |
critical |
FederationAllMembersDown |
no federation members scraped | critical |
8. Open items (long-term)
All critical and high-priority items are complete. What remains are long-term
production infrastructure improvements:
| Item | Priority | Description |
|---|---|---|
| Vault / AWS SSM | P3 | Replace env-var credentials with a secret manager |
| mTLS backends | P3 | Mutual TLS between verifiably-go and DPG backends |
| Volume backup | P3 | Daily backup of the verifiably-go-state volume |
| Push revocation | Backlog | Notify the holder when their credential is revoked |
| Docker socket (CREDEBL) | Backlog | agent-provisioning requires the Docker socket โ privilege risk |
VERIFIABLY_MODE=ui|api |
Backlog | Decouple the REST API from the HTMX frontend |
| did:web Walt.id automation | Post-Phase 5 | Walt.id supports did:web but the compose env var is missing |
9. Project numbers
| Metric | Value |
|---|---|
| Federation phases delivered | 13 / 13 (100%) |
| Security / bug items resolved (TODO.md) | 26 / 31 (84%) โ 5 remaining are long-tail |
| Supported DPGs | 5 (walt.id, CREDEBL, Inji Certify) |
| Demo running instances | 3 (Hub + MINERD + MT) |
| New external dependencies added | 0 (pure Go stdlib throughout) |