Integration Guide
This page walks through what it takes to integrate fido4vc-jcs-2026 verification into an existing verifier stack. The reference deployment uses walt.id — most of the worked examples below are walt.id-specific. The general patterns apply to any verifier.
Integration models
Section titled “Integration models”You have three architectural choices for how to add fido4vc-jcs-2026 verification to a verifier stack:
| Model | Pros | Cons |
|---|---|---|
HTTP sidecar (run fido-vc-verifier-sidecar next to your verifier) | Language-agnostic; one impl serves any backend; cleanest deployment for non-Node stacks | Extra deployment artifact; network hop adds latency; sidecar must be reachable |
Native in-process library (depend on fido-vc-cryptosuite-ts directly) | No network hop; no sidecar to operate; lowest latency | Language must match the cryptosuite port; only TS is published today, so non-Node verifier stacks must use the sidecar model |
| Hybrid (use native where you can, sidecar as fallback) | Best of both | More moving parts; need a strategy interface |
The walt.id reference deployment uses the sidecar model.
walt.id integration (HTTP sidecar)
Section titled “walt.id integration (HTTP sidecar)”walt.id’s waltid-verification-policies library provides a pluggable policy framework. Verification policies are registered by name; the verifier API applies policies declaratively based on the credential format.
Step 1 — Add the signature_ld-vp policy
Section titled “Step 1 — Add the signature_ld-vp policy”In waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/policies/LdSignaturePolicy.kt:
class LdSignaturePolicy(var baseUrl: String = DEFAULT_BASE_URL) : JwtVerificationPolicy() { override val name = "signature_ld-vp" override val description = "Checks a JSON-LD presentation by verifying its cryptographic signature " + "using the key referenced by the DID in `verificationMethod`." override val supportedVCFormats = setOf(VCFormat.ldp_vp)
companion object { // fido-vc-verifier-sidecar — assumes loopback colocation with walt.id; override for split-host deployments const val DEFAULT_BASE_URL = "http://localhost:8081" }
private val http = HttpClient { expectSuccess = false install(ContentNegotiation) { json() } defaultRequest { url(baseUrl) } }
override suspend fun verify( credential: String, args: Any?, context: Map<String, Any> ): Result<JsonElement> = runCatching { val ldp = Json.parseToJsonElement(credential).jsonObject val response = http.post("/verify") { setBody(ldp) header(HttpHeaders.ContentType, ContentType.Application.Json) } val body = response.body<JsonObject>() val verified = body["verified"]?.jsonPrimitive?.content?.toBoolean() ?: throw VerificationException("Invalid response format") if (!verified) { throw VerificationException( body["error"]?.jsonPrimitive?.content ?: "Signature verification failed" ) } ldp }}Register it in PolicyManager:
init { registerPolicies( JwtSignaturePolicy(), SdJwtVCSignaturePolicy(), LdSignaturePolicy(), // <-- add this // ... )}Step 2 — Route ldp_vc / ldp_vp through the new policy
Section titled “Step 2 — Route ldp_vc / ldp_vp through the new policy”In VerifierService.kt (or your equivalent format-routing layer):
when (format) { VCFormat.sd_jwt_vc -> listOf(PolicyRequest(SdJwtVCSignaturePolicy())) VCFormat.ldp_vc -> listOf(PolicyRequest(LdSignaturePolicy())) else -> listOf(PolicyRequest(JwtSignaturePolicy()))}Step 3 — Configure the issuer side
Section titled “Step 3 — Configure the issuer side”If your walt.id instance is also issuing credentials, advertise the cryptosuite in waltid-services/waltid-issuer-api/config/credential-issuer-metadata.conf:
credentialSupported = { # ... proof_types_supported = { ldp_vp = { proof_signing_alg_values_supported = ["fido4vc-jcs-2026"] } }}Step 4 — Deploy and run the sidecar
Section titled “Step 4 — Deploy and run the sidecar”Clone fido-vc-verifier-sidecar on the same machine (or in the same docker network / Kubernetes pod) as your walt.id verifier — LdSignaturePolicy posts to http://<sidecar-host>:8081/verify, with <sidecar-host> defaulting to localhost. The sidecar must be reachable from walt.id at whatever address <sidecar-host> resolves to (see Common pitfalls for non-loopback deployments):
git clone https://github.com/fido4vc/fido-vc-verifier-sidecarcd fido-vc-verifier-sidecarnpm installnpm run buildPORT=8081 npm startThe sidecar starts on port 8081, matching LdSignaturePolicy’s default <sidecar-host>:8081 target. For containerized deployments, run it alongside your walt.id container with the WALLET_API_URL env var pointing to the walt.id Wallet API.
Verifying the wiring
Section titled “Verifying the wiring”Use the included verifier test script in fido-vc-middleware/tests/walt-id-verifier.js:
{ "vp_policies": ["signature_ld-vp", "holder-binding"], "vc_policies": ["signature", "expired", "not-before"], "request_credentials": [ { "format": "jwt_vc_json", "type": "BankId" } ]}A successful flow: wallet submits a presentation with a fido4vc-jcs-2026 proof → walt.id’s signature_ld-vp policy fires → sidecar verifies → returns verified: true → walt.id returns success to the verifier.
General-backend integration (non-walt.id)
Section titled “General-backend integration (non-walt.id)”If you’re not using walt.id, the integration is conceptually identical:
- Detect the cryptosuite. When your verifier sees a VP with
proof.type = "DataIntegrityProof"andproof.cryptosuite = "fido4vc-jcs-2026", route to the verification logic. - Verify per the spec. Run the verify algorithm. Either:
- Call the sidecar over HTTP, or
- Import the TS reference implementation directly (for Node-based verifier stacks).
- Return success/failure to your verification pipeline. The cryptosuite verification is just one step — your pipeline still validates the embedded VCs, presentation definition, freshness, etc.
The cryptosuite is platform-agnostic; nothing about it is walt.id-specific.
Common pitfalls
Section titled “Common pitfalls”Sidecar not reachable. LdSignaturePolicy calls http://<sidecar-host>:8081/verify, where <sidecar-host> defaults to localhost. That default only works when the sidecar shares a loopback with walt.id (same host, same docker network, or same Kubernetes pod — containers in one pod share localhost). For split deployments, override baseUrl to a reachable address — e.g. LdSignaturePolicy(baseUrl = "http://sidecar.internal:8081") for a private DNS host, or a Kubernetes Service DNS name like http://fido-sidecar.default.svc.cluster.local:8081.
JCS implementation drift. If signatures from a JS signer don’t verify under a JVM verifier (or vice versa), the cause is almost always a non-RFC-8785-compliant JCS implementation. Use Erdtman’s reference on both sides — canonicalize npm package for JS, io.github.erdtman:java-json-canonicalization for JVM.
Wallet returning the wrong proof structure. proofValue MUST be an object with signature, authenticatorData, clientData. Some VC-DI libraries expect a string here and will reject the proof at parse time. If you see schema errors at the verifier, check that the wallet implementation is emitting the structured object format.
origin mismatch. WebAuthn’s clientDataJSON.origin must match the FIDO middleware’s configured origin. If your middleware moves to a new domain, all previously-registered FIDO credentials become unusable (this is a WebAuthn invariant, not a FIDO4VC choice).
Deployment topology
Section titled “Deployment topology”A complete reference deployment looks like:
[Browser: Wallet UI] ← Next.js ↓ HTTP[FIDO Middleware] ← Express, port 8080 ↓ HTTP[walt.id Wallet API] ← Kotlin/JVM, port 7001 ↓ HTTP / outbound[Issuer API + Verifier API] ← walt.id, ports 7002/7003 ↓ HTTP (verifier policy)[fido-vc-verifier-sidecar] ← Express, port 8081 ↓ (in-process)[fido-vc-cryptosuite-ts]Plus a database for the FIDO Middleware’s WebAuthn credential records and walt.id’s wallet storage.
A docker-compose.yml for the full local stack is in the parent waltid-identity fork.
Where to next
Section titled “Where to next”- Specification — normative cryptosuite spec
- Repositories — the four standalone components