Verifier Integration

Link the Trust Registry to a Verifier2 Service to enable automatic trust verification during credential presentation. The etsi-trust-list policy matches issuer certificates — or, for issuers with no certificate, issuer public keys — against trust sources already loaded in the registry, without fetching the trust list again. A credential with no x5c still needs its signing key resolved first. did:jwk needs no network call; other DID methods and HTTPS issuer metadata do. See Public-Key and DID-Based Issuers.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                     Verification Flow                            │
│                                                                  │
│  Credential  ──►  Verifier2  ──►  etsi-trust-list policy        │
│                       │                    │                     │
│                       │ dependency         │ resolves via        │
│                       ▼                    ▼                     │
│              Trust Registry Service  ◄─────┘                     │
│                       │                                          │
│                       │ queries                                  │
│                       ▼                                          │
│                   Loaded Trust Sources (TSL, LoTE)               │
└─────────────────────────────────────────────────────────────────┘

Prerequisites

  1. A Trust Registry Service with at least one trust source loaded (see Setup and Trust Source Management).
  2. A Verifier2 Service in the same tenant.

Add the Trust Registry as a dependency of your Verifier2 service:

CURL

Endpoint: /v2/{target}/verifier-service-api/dependencies/add

Example Request

curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{verifier2Target}/verifier-service-api/dependencies/add' \
  -H 'accept: */*' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "dependency": "myorg.tenant1.trust-registry"
}'

Path Parameters

  • orgID: String — Your organization's Base URL prefix.
  • verifier2Target: String — The Verifier2 service path, e.g. myorg.tenant1.verifier2.

Header Parameters

  • Authorization: String — Bearer token for authentication. Format: Bearer {yourToken}.

Body Parameters

  • dependency: String — The full path to the Trust Registry service, e.g. myorg.tenant1.trust-registry.

Response Codes

  • 201 — Dependency added successfully.

List the Verifier2 dependencies to confirm the Trust Registry is linked:

CURL

Endpoint: GET /v2/{verifier2Target}/verifier-service-api/dependencies/list

Example Request
curl -X 'GET' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{verifier2Target}/verifier-service-api/dependencies/list' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}'

Path Parameters

  • orgID: String — Your organization's Base URL prefix.
  • verifier2Target: String — The Verifier2 service path, e.g. myorg.tenant1.verifier2.

Header Parameters

  • Authorization: String — Bearer token for authentication. Format: Bearer {yourToken}.

Example Response

Returns an array of all services registered as dependencies of the Verifier2 service.

[
  {
    "_id": "myorg.tenant1.trust-registry",
    "type": "trust-registry"
  }
]

Use the etsi-trust-list Policy

The etsi-trust-list policy is a standard Verifier2 VC policy. You add it to the vc_policies array when creating a verification session — the same way you would add signature or any other policy.

The policy snippet looks like this:

{
  "policy": "etsi-trust-list",
  "expectedEntityType": "PID_PROVIDER",
  "allowStaleSource": false,
  "requireAuthenticated": true
}

Place this inside the policies.vc_policies array of your session creation request body. For a complete walkthrough of creating a verification session — including the full request structure, flow_type, dcql_query, and response fields — see Credential Verification.

When Verifier2 has the Trust Registry linked as a dependency (as set up above), the policy resolves certificates automatically using the pre-loaded trust sources. No additional URL or inline configuration is required.

Certificate Chain Construction

When the presented credential's header contains an x5c certificate chain (always the case for mDoc COSE headers; the common case for SD-JWT/JWT VC), the policy sends the complete leaf-first chain to the linked Trust Registry. The registry uses certificates stored in loaded trust sources as independent trust anchors and performs PKIX path building.

The credential should contain:

  1. the credential-signing leaf certificate;
  2. any intermediate certificates required to reach the registered anchor.

Do not include the root trust anchor when the applicable profile prohibits it. OpenID4VC HAIP 1.0, Section 6.1.1 states that the X.509 trust-anchor certificate MUST NOT be included in the SD-JWT VC x5c header. The Trust Registry must contain the anchor certificate itself, not only its SHA-256 fingerprint, so it can construct the certification path.

Public-Key and DID-Based Issuers

Not every SD-JWT/JWT VC issuer holds an X.509 certificate. An issuer that signs with a did:web (or other DID method) key typically has no x5c header at all - only kid and iss. ETSI TS 119 602 allows registering such an issuer in a LoTE trust list by public key alone (see Public-Key-Only Entities), and the etsi-trust-list policy resolves trust for these issuers automatically:

  1. If the credential header has no x5c, the policy resolves the issuer's signing public key itself - via DID resolution (following iss, matching the verification method by kid) or, for an HTTPS iss, JWT VC issuer metadata at its well-known endpoint. This is a network call except for did:jwk, which is self-contained.
  2. The resolved public key is matched against the loaded trust list by RFC 7638 SHA-256 JWK thumbprint, the same way Resolve by Public Key works when called directly. This match stays inside the Trust Registry.

Policy Resolution Modes

The etsi-trust-list policy supports three resolution modes. Choose the one that fits your deployment:

If more than one mode is configured, precedence is remote service (trustRegistryUrl), inline sources (trustLists), then the linked Enterprise service.

Uses the Trust Registry you linked in the previous step. The policy discovers and queries it automatically at verification time — no extra configuration needed.

{
  "policy": "etsi-trust-list",
  "expectedEntityType": "PID_PROVIDER"
}

This is the recommended mode for enterprise deployments. Trust lists are pre-loaded into the Trust Registry Service, so the registry match itself makes no outbound call. A credential with no x5c can still require a network call to resolve the issuer key, as described in Public-Key and DID-Based Issuers.

2. Remote Service Mode

Queries a compatible external Trust Registry API. The policy appends /trust-registry/resolve/certificate-chain to trustRegistryUrl when the credential has an x5c chain, or /trust-registry/resolve/public-key when it doesn't (see Public-Key and DID-Based Issuers). It does not attach an Enterprise bearer token.

{
  "policy": "etsi-trust-list",
  "trustRegistryUrl": "https://trust-registry.example.com",
  "expectedEntityType": "PID_PROVIDER"
}

trustRegistryUrl is not an Enterprise service target URL. Use linked service mode for an Enterprise Trust Registry dependency. Remote mode requires an API that exposes POST /trust-registry/resolve/certificate-chain and POST /trust-registry/resolve/public-key with suitable network access and authentication.

3. Inline Mode

Fetches and processes a trust list directly at verification time, without a Trust Registry service. Suitable for testing or simple deployments where operating a dedicated Trust Registry Service is not warranted.

{
  "policy": "etsi-trust-list",
  "trustLists": [
    "https://www.signatur.rtr.at/vertrauensliste.xml"
  ],
  "expectedEntityType": "PID_PROVIDER"
}

Inline Mode fetches the trust list on every verification request. This adds latency and creates a runtime dependency on the availability of the external URL. Avoid this mode in production.

With validateSignatures: true, inline mode verifies source signature integrity. Use requireAuthenticated: true together with independently configured signer certificates when the source signer must also be authorized.

Policy Configuration Options

The following parameters are supported across all resolution modes unless noted otherwise:

ParameterTypeDefaultDescription
expectedEntityTypestringnullFilter by entity type. Valid values: PID_PROVIDER, WALLET_PROVIDER, ATTESTATION_PROVIDER, TRUST_SERVICE_PROVIDER, ACCESS_CERTIFICATE_PROVIDER, RELYING_PARTY_PROVIDER, OTHER. When omitted, no entity type filter is applied.
expectedServiceTypestringnullFilter by service type URI. When omitted, no service type filter is applied.
allowStaleSourcebooleanfalseWhen true, accepts STALE_SOURCE decisions and TRUSTED decisions whose source freshness is STALE. A STALE_SOURCE decision can represent an expired source, so keep this false for strict verification.
requireAuthenticatedbooleanfalseWhen true, requires AUTHENTICATED source assurance in every freshness branch. FAILED assurance is always rejected. When false, INTEGRITY_VERIFIED, UNVERIFIED, or UNKNOWN may be accepted if the source was admitted by its configured policy.
trustRegistryUrlstring—(Remote mode only) Base URL of a compatible service exposing /trust-registry/resolve/certificate-chain.
trustListsarray of strings—(Inline mode only) One or more trust list URLs or raw XML/JSON/JWS values to process at verification time.
validateSignaturesbooleantrue(Inline mode only) When true, verifies XMLDSig or compact-JWS signatures when present. When false, signature verification is skipped.
trustedSourceSignerCertificatesarray of strings[](Inline mode only) PEM or Base64-DER signer certificates or trust anchors used to authenticate XMLDSig and compact-JWS sources.

Example Configurations

Strict linked-service mode — entity type enforced, stale sources rejected, and the previously loaded source must be authenticated:

{
  "policy": "etsi-trust-list",
  "expectedEntityType": "PID_PROVIDER",
  "allowStaleSource": false,
  "requireAuthenticated": true
}

The linked Trust Registry must have admitted the source with REQUIRE_AUTHENTICATED; setting requireAuthenticated on the verification policy does not authenticate a source retroactively.

Permissive development mode — useful for controlled testing when trust sources are not independently authenticated:

{
  "policy": "etsi-trust-list",
  "expectedEntityType": "PID_PROVIDER",
  "allowStaleSource": true,
  "requireAuthenticated": false
}

Verification Result

Once a wallet has responded to the verification request and the session is complete, retrieve the session result using the session info endpoint. The policyResults field contains one entry per applied policy.

For the etsi-trust-list policy, a successful result looks like this:

{
  "policy": "etsi-trust-list",
  "success": true,
  "result": {
    "trusted": true,
    "decision": "TRUSTED",
    "matchedEntity": {
      "entityId": "entity-123",
      "entityType": "PID_PROVIDER",
      "legalName": "Example PID Provider",
      "country": "DE"
    },
    "matchedService": {
      "serviceId": "service-456",
      "serviceType": "http://uri.etsi.org/TrstSvc/Svctype/IdV",
      "status": "GRANTED"
    },
    "matchedSource": {
      "sourceId": "pid-providers",
      "sourceFamily": "LOTE",
      "format": "ETSI_TS_119_602_JSON",
      "displayName": "EU PID Providers"
    },
    "sourceFreshness": "FRESH",
    "authenticity": "AUTHENTICATED"
  }
}

Result fields:

  • trusted: Always true for a successful policy result.
  • decision: The trust resolution outcome. TRUSTED means the issuer was found in a valid trusted source. STALE_SOURCE means the source was found but is stale — only returned as a success when allowStaleSource: true is set.
  • matchedEntity (optional): The entity in the trust source whose certificate matched.
    • entityId: The entity's identifier within the trust source.
    • entityType: The type of the entity, e.g. PID_PROVIDER, WALLET_PROVIDER, ATTESTATION_PROVIDER.
    • legalName: The registered legal name of the entity.
    • country (optional): The country code of the entity, when available.
  • matchedService (optional): The specific trust service entry that matched the certificate.
    • serviceId: The service's identifier within the trust source.
    • serviceType: The service type URI as defined in the trust list.
    • status: The service status URI as defined in the trust list.
  • matchedSource: The trust source that produced the match.
    • sourceId: The ID of the trust source as registered in the Trust Registry.
    • sourceFamily: The trust list family, e.g. TSL or LOTE.
    • displayName: The human-readable name of the trust source.
  • sourceFreshness: Freshness state of the trust source at verification time. Possible values: FRESH, STALE, EXPIRED, UNKNOWN.
  • authenticity: Source assurance projection. Possible values: AUTHENTICATED, INTEGRITY_VERIFIED, UNVERIFIED, FAILED, or UNKNOWN.
  • warnings (optional): Array of warning strings, present only when the policy succeeded with caveats (e.g. stale source accepted).

Troubleshooting Verification

  • No trust source configured: link a Trust Registry dependency, set trustRegistryUrl, or provide trustLists.
  • JWT has no x5c certificate chain in header, and the issuer's public key could not be resolved: the credential has no x5c, and the issuer's iss is not a resolvable DID and not an HTTPS URL serving JWT VC issuer metadata. Verify the DID document is reachable and its verification method matches the credential's kid, or register the issuer's certificate instead.
  • Certificate not trusted / NOT_TRUSTED for a DID-based issuer: the issuer resolved successfully, but no PublicKeyValues entity in the loaded trust list matches its JWK thumbprint. Confirm the trust list registers the issuer's current signing key — see Public-Key-Only Entities.
  • Trust source is not authenticated: load the source with REQUIRE_AUTHENTICATED and an independently trusted signer certificate, or correct the inline signer configuration.
  • Trust source is stale or expired: refresh the source and inspect its nextUpdate value. Enable allowStaleSource only after assessing the risk.
  • No valid path: ensure the credential presents the leaf and required intermediates, while the Trust Registry stores the full anchor certificate.
Last updated on September 28, 2026