---
title: "Verifying Verifiable Credentials (W3C) via OID4VP"
description: "Learn how to request and verify Verifiable Credentials (W3C) via OID4VP using the walt.id Verifier2 API."
stack: "Community Stack (open source) — version 0.21.0"
stack_version: "0.21.0"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp
generated: 2026-07-20
---
# Verifying Verifiable Credentials (W3C) via OID4VP

This guide shows you how to verify a W3C Verifiable Credential using the walt.id Verifier2 API and the OID4VP protocol. The verification process sends a credential request to a holder's wallet, receives the signed credential presentation, and validates it against your policies.

[**Verifiable Credential (VC)**](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md): A digital equivalent of physical credentials such as a driver's license or university degree. VCs are cryptographically secure, privacy-respecting, and instantly verifiable.

[**OID4VP**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md): A protocol specifying how parties can present VCs in a way that's consistent and secure across platforms ensuring interoperability.

**Note:**

If you're migrating from the original Verifier (v1), see the [Migration Guide](https://docs.walt.id/community-stack/verifier2/migration-from-verifier1.md).

## Prerequisites

Before you begin, ensure you have:

- **Verifier2 API running** — Follow the [Getting Started](https://docs.walt.id/community-stack/verifier2/getting-started.md) or [Setup](https://docs.walt.id/community-stack/verifier2/setup.md) guide
- **A test wallet with credentials** — Use the [walt.id Wallet](https://docs.walt.id/community-stack/wallet/getting-started.md) for testing, or any OID4VP-compatible wallet
- **Publicly accessible URL** (for local development) — Use [ngrok](https://ngrok.com) or similar to expose `http://localhost:7003` if testing with a mobile wallet

**Note:**

**Local Development:** When running locally via Docker, the API is available at `http://localhost:7003` (standalone) or `http://localhost:7004` (Docker Compose). Use this as your base URL in the examples below.

---

## Example 1: Basic Verification

This example requests a single W3C Verifiable Credential (OpenBadgeCredential) and verifies its cryptographic signature using the default signature policy.

**Option: CURL**

**Endpoint:** `POST /verification-session/create` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/post_verification_session_create)

##### Example Request

```bash
curl -X 'POST' \
  'http://localhost:7003/verification-session/create' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "flow_type": "cross_device",
  "core_flow": {
    "dcql_query": {
      "credentials": [
        {
          "id": "credential_1",
          "format": "jwt_vc_json",
          "meta": {
            "type_values": [
              [
                "VerifiableCredential",
                "UniversityDegree"
              ]
            ]
          }
        }
      ]
    },
    "policies": {
      "vc_policies": [
        {
          "policy": "signature"
        }
      ]
    }
  }
}'
```

**Body Parameters**

- **flow_type**: _String_ (required) - Specifies how the credential request will be delivered to the holder's wallet. Options:
  - `"cross_device"` — User scans a QR code on a different device (e.g. desktop browser → phone wallet)
  - `"same_device"` — User clicks a deep link on the same device (e.g. phone browser → wallet app on same phone)
  - `"dc_api"` — Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) over OpenID4VP 1.0 (Annex D).
  - `"dc_api-annex-c"` — Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) over 18013-7 (Annex C).

- **core_flow**: _Object_ (required) - Defines what credentials to request and which policies to apply. Contains:
  - **dcql_query**: _Object_ (required) - Specifies the credential type, format, and claims to request using the [DCQL query language](https://identity.foundation/credential-query-language/). You can also use the DCQL builder [here](https://dcql.walt.id/).
    - **credentials**: _Array_ - List of credential queries. Each credential query contains:
      - **id**: _String_ - Unique identifier for this credential in the request (used in policy results)
      - **format**: _String_ - Credential format. For W3C VCs, use the `"jwt_vc_json"` (JWT format).
      - **meta.type_values**: _Array_ - List of acceptable credential type arrays. The credential must match at least one type array. Example: `[["VerifiableCredential", "OpenBadgeCredential"]]`.
      - **claims**: _Array_ (optional) - Specific claims to request from the credential. Each claim has a `path` (JSON path to the claim). If omitted, all claims that are non-selectively disclosable are provided by the wallet.
  - **policies**: _Object_ (optional) - Validation rules applied to the received credential. Defaults to signature verification only if omitted.
    - **vc_policies**: _Array_ - Policies applied to individual credentials. Each policy has a `policy` field specifying the policy name. Available policies: `signature`, `expired`, `not-before`, `revoked-status-list`, `schema`, and more. See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for the complete list.
    - **vp_policies**: _Array_ (optional) - Policies applied to the entire presentation (not shown in this example)
  - **signedRequest**: _Boolean_ (optional) - Whether to sign the authorization request JWT. Required for HAIP profile compliance and DC API Annex C (ISO 18013-7) flows. Defaults to `false`. See [Signed Requests](#signed-requests) to learn more.
  - **encryptedResponse**: _Boolean_ (optional) - Whether the wallet should encrypt the VP token response. Defaults to `false`. See [Encrypted Responses](#encrypted-responses) to learn more.
  - **key**: _Object_ (optional) - Signing key for this verification session. Overrides the service-wide default `key` configured via the[`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional). Must be a serialized key in the format `{"type":"jwk","jwk":{...}}`. Required when `signedRequest: true` or `encryptedResponse: true` and no service-wide default is configured. See [Signed Requests](#signed-requests) and [Encrypted Responses](#encrypted-responses) for when this is needed.
  - **x5c**: _Array of Strings_ (optional) - X.509 certificate chain for this verification session. Overrides the service-wide default `x5c` configured via  [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#x5c-optional). Must be an ordered chain in DER-encoded, Base64-encoded format where the first certificate corresponds to the `key`. Required for X.509-based client authentication (`x509_san_dns:` or `x509_hash:` as `clientId`) and signed Annex C requests. See [Signed Requests](#signed-requests) to learn more.
  - **verifier_info**: _Object_ (optional) - An array of attestations about the Verifier relevant to the Credential Request. Each object is a JSON object with the following fields:
    - **format**: _String_ - The format of the attestation. Currently only `"jwt"` is supported.
    - **data**: _String_ - The attestation data in JWT format.
    - **credentialIds**: _Array_ (optional) - Specifies which credentials in your DCQL query this attestation applies to. References the `id` fields from your `credentials` array in the DCQL query. If omitted, the attestation applies to all requested credentials.
    
- **url_config**: _Object_ (optional) - URL configuration for this session. Only applies to `cross_device` and `same_device` flows (not `dc_api`).
  - **url_prefix**: _String_ (optional) - The publicly accessible base URL where the wallet will fetch the authorization request and post its response. Overrides the `urlPrefix` from `verifier-service.conf`.
    - The service constructs: Request URI: `{urlPrefix}/{sessionId}/request` and Response URI: `{urlPrefix}/{sessionId}/response`
    - **Must be reachable by the wallet** (use ngrok or public URL for local development)
  - **url_host**: _String_ (optional) - The base URL used to construct the authorization request link. Overrides the `urlHost` from `verifier-service.conf`.
    - For standard OID4VP flows, use `"openid4vp://authorize"` (default)
    - For custom authorization endpoints (e.g. conformance testing), provide a full HTTPS URL

- **redirects**: _Object_ (optional) - Success and error redirect URLs (only for `cross_device` and `same_device` flows)
  - **successRedirectUri**: _String_ - URL to redirect the user after successful verification
  - **errorRedirectUri**: _String_ - URL to redirect the user if verification fails

**Note:**

  Many parameters have defaults configured in `verifier-service.conf`. Request-level parameters override these defaults for that specific session only. See [Configuration Defaults](#configuration-defaults) below.

---

##### Example Response

The response contains the session ID and authorization URLs to present to the user.

```json
{
  "sessionId": "7c66c445-63e6-4a6a-8351-23ec78791b4e",
  "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=verifier2&request_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F7c66c445-63e6-4a6a-8351-23ec78791b4e%2Frequest",
  "fullAuthorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=verifier2&state=467f4e16-8c42-489a-a17c-cb6175063df3&response_mode=direct_post&nonce=ae4a922a-255f-4118-b9c6-35031c3546ff&response_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F7c66c445-63e6-4a6a-8351-23ec78791b4e%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22credential_1%22%2C%22format%22%3A%22jwt_vc_json%22%2C%22meta%22%3A%7B%22type_values%22%3A%5B%5B%22VerifiableCredential%22%2C%22UniversityDegree%22%5D%5D%7D%7D%5D%7D&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22mso_mdoc%22%3A%7B%22issuerauth_alg_values%22%3A%5B-7%2C-9%2C-50%5D%2C%22deviceauth_alg_values%22%3A%5B-7%2C-9%2C-50%2C-65537%5D%7D%7D%2C%22client_name%22%3A%22Verifier2%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fimages.squarespace-cdn.com%2Fcontent%22%2C%22tos_uri%22%3A%22https%3A%2F%2Fexample.com%2Fterms%22%2C%22policy_uri%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy%22%2C%22client_uri%22%3A%22https%3A%2F%2Fverifier.example.com%22%2C%22client_name%23fr-FR%22%3A%22V%C3%A9rificateur2%22%2C%22logo_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Flogo-fr.png%22%2C%22tos_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fterms-fr%22%2C%22policy_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy-fr%22%2C%22client_uri%23fr-FR%22%3A%22https%3A%2F%2Fverifier.example.com%2Ffr%22%7D"
}
```

**Response Fields:**

- **sessionId**: _String_ - The unique ID of this verification session. Use this to query the session status.
- **bootstrapAuthorizationRequestUrl**: _String_ - Shortened URL using `request_uri` by reference. **Use this for QR codes** to minimize QR code density.
- **fullAuthorizationRequestUrl**: _String_ - Complete URL with all parameters embedded. Use this if the wallet doesn't support `request_uri` (rare).

---

## Presenting the Request to the User

After creating the session, you need to present the authorization request to the user based on the flow type you chose.

### Cross-Device Flow (QR Code)

1. **Generate a QR code** from the `bootstrapAuthorizationRequestUrl`
2. **Display the QR code** to the user on your web page or application
3. **User scans the QR code** with their wallet app
4. **Wallet presents the credential** and posts the response to your verifier
5. **Monitor the session status** by polling the session endpoint or using SSE/callbacks (see below)

### Same-Device Flow (Deep Link)

1. **Redirect the user** to the `bootstrapAuthorizationRequestUrl`
2. **User's wallet app opens** automatically (via deep link)
3. **Wallet presents the credential** and posts the response to your verifier
4. **User is redirected** to your `successRedirectUri` or `errorRedirectUri`
5. **Query the session status** to retrieve the verification results

---

## Checking Verification Results

You can query the verification session to check if the user has presented their credential and whether validation succeeded.

**Option: CURL**

**Endpoint:** `GET /verification-session/{sessionId}/info` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/get_verification_session__verification_session__info)

##### Example Request

```bash
curl -X 'GET' \
  'http://localhost:7003/verification-session/{sessionId}/info' \
  -H 'accept: application/json'
```

**Path Parameters**

- **sessionId**: _String_ (required) - The unique ID of the verification session returned when you created the session. Use this to query the current status and results.

---

##### Example Response

The response contains the complete session information including setup, status, policy results, and presented credentials.

```json
{
  "id": "7c66c445-63e6-4a6a-8351-23ec78791b4e",
  "setup": {
    "flow_type": "cross_device",
    "core_flow": {
      "dcql_query": {
        "credentials": [
          {
            "id": "credential_1",
            "format": "jwt_vc_json",
            "meta": {
              "type_values": [
                ["VerifiableCredential", "UniversityDegree"]
              ]
            }
          }
        ]
      },
      "policies": {
        "vc_policies": [
          {
            "policy": "signature",
            "id": "signature"
          }
        ]
      }
    }
  },
  "creationDate": "2026-05-13T13:55:38.766985012Z",
  "expirationDate": "2026-05-13T14:00:38.766985012Z",
  "retentionDate": "2036-05-13T13:55:38.766985012Z",
  "status": "SUCCESSFUL",
  "attempted": true,
  "reattemptable": true,
  "bootstrapAuthorizationRequest": {
    "client_id": "verifier2",
    "request_uri": "https://f5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app/verification-session/7c66c445-63e6-4a6a-8351-23ec78791b4e/request"
  },
  "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=verifier2&request_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F7c66c445-63e6-4a6a-8351-23ec78791b4e%2Frequest",
  "authorizationRequest": {
    "response_type": "vp_token",
    "client_id": "verifier2",
    "state": "467f4e16-8c42-489a-a17c-cb6175063df3",
    "response_mode": "direct_post",
    "nonce": "ae4a922a-255f-4118-b9c6-35031c3546ff",
    "response_uri": "https://f5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app/verification-session/7c66c445-63e6-4a6a-8351-23ec78791b4e/response",
    "dcql_query": {
      "credentials": [
        {
          "id": "credential_1",
          "format": "jwt_vc_json",
          "meta": {
            "type_values": [
              ["VerifiableCredential", "UniversityDegree"]
            ]
          }
        }
      ]
    }
  },
  "authorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=verifier2&state=467f4e16-8c42-489a-a17c-cb6175063df3&response_mode=direct_post&nonce=ae4a922a-255f-4118-b9c6-35031c3546ff&response_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F7c66c445-63e6-4a6a-8351-23ec78791b4e%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22credential_1%22%2C%22format%22%3A%22jwt_vc_json%22%2C%22meta%22%3A%7B%22type_values%22%3A%5B%5B%22VerifiableCredential%22%2C%22UniversityDegree%22%5D%5D%7D%7D%5D%7D&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22mso_mdoc%22%3A%7B%22issuerauth_alg_values%22%3A%5B-7%2C-9%2C-50%5D%2C%22deviceauth_alg_values%22%3A%5B-7%2C-9%2C-50%2C-65537%5D%7D%7D%2C%22client_name%22%3A%22Verifier2%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fimages.squarespace-cdn.com%2Fcontent%22%2C%22tos_uri%22%3A%22https%3A%2F%2Fexample.com%2Fterms%22%2C%22policy_uri%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy%22%2C%22client_uri%22%3A%22https%3A%2F%2Fverifier.example.com%22%2C%22client_name%23fr-FR%22%3A%22V%C3%A9rificateur2%22%2C%22logo_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Flogo-fr.png%22%2C%22tos_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fterms-fr%22%2C%22policy_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy-fr%22%2C%22client_uri%23fr-FR%22%3A%22https%3A%2F%2Fverifier.example.com%2Ffr%22%7D",
  "requestMode": "REQUEST_URI",
  "policies": {
    "vp_policies": {
      "jwt_vc_json": [
        "jwt_vc_json/audience-check",
        "jwt_vc_json/nonce-check",
        "jwt_vc_json/envelope_signature"
      ]
    },
    "vc_policies": [
      {
        "policy": "signature",
        "id": "signature"
      }
    ]
  },
  "policy_results": {
    "vp_policies": {
      "credential_1": {
        "jwt_vc_json/audience-check": {
          "policy_executed": {
            "policy": "jwt_vc_json/audience-check",
            "description": "Check if presentation audience matches expected audience for session"
          },
          "success": true,
          "results": {
            "presentation_audience": "verifier2",
            "expected_audience": "verifier2"
          }
        },
        "jwt_vc_json/nonce-check": {
          "policy_executed": {
            "policy": "jwt_vc_json/nonce-check",
            "description": "Check if presentation nonce matches expected nonce for session"
          },
          "success": true
        },
        "jwt_vc_json/envelope_signature": {
          "policy_executed": {
            "policy": "jwt_vc_json/envelope_signature",
            "description": "Verify signature using holders public key"
          },
          "success": true
        }
      }
    },
    "vc_policies": [
      {
        "policy": {
          "policy": "signature",
          "id": "signature"
        },
        "success": true,
        "result": {
          "verification_result": true,
          "verified_data": {
            "iss": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii...",
            "sub": "did:key:zmYg9bgKmRiCqTTd9MA1ufVE9tfzUptwQp4GMRxptXquJWw...",
            "vc": {
              "type": ["VerifiableCredential", "UniversityDegree"],
              "issuer": {
                "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii..."
              },
              "credentialSubject": {
                "id": "did:key:zmYg9bgKmRiCqTTd9MA1ufVE9tfzUptwQp4GMRxptXquJWw...",
                "degree": {
                  "type": "BachelorDegree",
                  "name": "Bachelor of Science and Arts"
                }
              }
            }
          }
        }
      }
    ],
    "overallSuccess": true
  },
  "presented_credentials": {
    "credential_1": [
      {
        "type": "vc-w3c_1_1",
        "credentialData": {
          "type": ["VerifiableCredential", "UniversityDegree"],
          "issuer": {
            "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii..."
          },
          "issuanceDate": "2026-05-13T13:03:43.475820800Z",
          "credentialSubject": {
            "id": "did:key:zmYg9bgKmRiCqTTd9MA1ufVE9tfzUptwQp4GMRxptXquJWw...",
            "degree": {
              "type": "BachelorDegree",
              "name": "Bachelor of Science and Arts"
            }
          },
          "expirationDate": "2027-05-13T13:03:43.475849300Z"
        },
        "issuer": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii...",
        "subject": "did:key:zmYg9bgKmRiCqTTd9MA1ufVE9tfzUptwQp4GMRxptXquJWw..."
      }
    ]
  }
}
```

**Key Response Fields:**

- **id**: _String_ - The verification session ID
- **setup**: _Object_ - The complete session configuration that was used to create this verification session (the `VerificationSessionSetup` from the create request)
- **creationDate**: _String_ - ISO 8601 timestamp when the session was created
- **expirationDate**: _String_ - ISO 8601 timestamp when the session expires (default: 10 minutes after creation). Session will expire if left unused (no presentation pushed). Once a presentation is received, the session is no longer eligible for expiry.
- **status**: _String_ - The current status of the verification session. Possible values:
  - `"UNKNOWN"` — Session ended up in unknown flow (should be avoided)
  - `"ACTIVE"` — Session was created and is active (can be used)
  - `"UNUSED"` — Session was not used yet, but not expired (can be used)
  - `"IN_USE"` — AuthorizationRequest was requested by wallet
  - `"VALIDATING_RECEIVED_REQUEST"` — Checking if received presentation will be processed
  - `"PROCESSING_FLOW"` — Received presentation is being processed (validation + policy execution)
  - `"EXPIRED"` — Verification request expired without being utilized
  - `"SUCCESSFUL"` — Verification completed fully successfully (all validation & policies passed)
  - `"FAILED"` — Verification was unsuccessful (presentation validation or policies failed)
- **attempted**: _Boolean_ - Whether the wallet has attempted to respond to the verification request
- **reattemptable**: _Boolean_ - Whether the user can retry this verification session if it failed
- **policies**: _Object_ - The policies configured for this session:
  - **vp_policies**: _Object_ (optional) - Policies to run on presentations
  - **vc_policies**: _Array_ (optional) - Policies to run on credentials
  - **specific_vc_policies**: _Object_ (optional) - Policies to run on specific credential IDs
- **policy_results**: _Object_ (optional) - Detailed results for all policies applied to the verification (only present after presentation is received):
  - **vp_policies**: _Object_ - Results for presentation-level policies (audience check, nonce check, envelope signature), organized by credential ID. Structure: `{ "credential_id": { "policy_name": { ... } } }`
  - **vc_policies**: _Array_ - Results for credential-level policies (signature, expiry, schema, etc.). Each item contains `policy`, `success`, and `result`.
  - **specific_vc_policies**: _Object_ - Results for credential-specific policies
  - **overallSuccess**: _Boolean_ (computed) - Whether all policies passed
- **redirects**: _Object_ (optional) - Success and error redirect URIs if configured

**Note:**

Instead of polling the session status, you can use **callbacks** or **Server Sent Events (SSE)** to be notified when the verification completes. See [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) for details.

---

## Example 2: Requesting Multiple Credentials

You can request multiple credentials in a single verification session. The wallet will present all matching credentials, and policies will be applied to each one.

This example requests both an OpenBadgeCredential and a VerifiableId in JWT format.

**Option: CURL**

**Endpoint:** `POST /verification-session/create` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/post_verification_session_create)

##### Example Request

```bash
curl -X 'POST' \
  'http://localhost:7003/verification-session/create' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "flow_type": "cross_device",
  "core_flow": {
    "dcql_query": {
      "credentials": [
        {
          "id": "example_openbadge_jwt_vc",
          "format": "jwt_vc_json",
          "meta": {
            "type_values": [
              [
                "VerifiableCredential",
                "OpenBadgeCredential"
              ]
            ]
          },
          "claims": [
            {
              "path": [
                "name"
              ]
            }
          ]
        },
        {
          "id": "example_verifiableid_jwt_vc",
          "format": "jwt_vc_json",
          "meta": {
            "type_values": [
              [
                "VerifiableCredential",
                "VerifiableAttestation",
                "VerifiableId"
              ]
            ]
          },
          "claims": [
            {
              "path": [
                "issuanceDate"
              ]
            }
          ]
        }        
      ]
    },
    "policies": {
      "vc_policies": [
        {
          "policy": "signature"
        }
      ]
    }
  }
}'
```

The wallet will present both credentials (if available), and the signature policy will be applied to each one. The `policyResults` in the session info response will contain separate results for each credential using their `id` values.

---

## Understanding Flow Types and Core Concepts

Now that you've seen verification in action, let's understand the underlying structure.

### Verification Session Structure

A `VerificationSession` defines the full lifecycle: authorization request → user presentation → policy validation → final result.

Every verification request follows this structure:

```json
{
  "flow_type": "cross_device | same_device | dc_api",
  "core_flow": { /* common to all flows */ },
  "url_config": { /* specific to cross_device and same_device */ },
  "redirects": { /* optional */ }
}
```

**Why this structure?**

- **flow_type** determines how the credential request is delivered (QR code vs deep link vs browser API)
- **core_flow** contains the credential query and policies (same regardless of delivery method)
- **url_config** and **redirects** are flow-specific attributes

This separation makes it easy to switch between flows without changing your credential requirements.

### Flow Type Comparison

| Flow Type | Use Case | Required Attributes | User Experience |
|-----------|----------|---------------------|-----------------|
| `cross_device` | User scans QR code from desktop | None beyond core flow | Desktop shows QR → User scans with phone → Phone wallet opens |
| `same_device` | User clicks link on mobile | `redirects` (success/error URLs) | Browser redirects to wallet → Wallet opens → User returns to browser |
| `dc_api` | Browser-native credential exchange | `expectedOrigins` (array of allowed origins) | Browser shows native credential picker → User selects → Instant return |

---

## Configuration Defaults

Many session parameters can be configured globally in `verifier-service.conf` and overridden per-request when needed.

| Parameter | Config File Location | Request Override Path | When to Override |
|-----------|---------------------|----------------------|------------------|
| `urlPrefix` | `verifier-service.conf` → `urlPrefix` | `url_config.url_prefix` | Testing with ngrok, different environments per session |
| `urlHost` | `verifier-service.conf` → `urlHost` | `url_config.url_host` | Custom authorization endpoints, conformance testing |
| `clientId` | `verifier-service.conf` → `clientId` | `core_flow.clientId` | Different verifier identities per use case |
| `clientMetadata` | `verifier-service.conf` → `clientMetadata` | `core_flow.client_metadata` | Session-specific branding, multi-tenant scenarios |
| Signing key | `verifier-service.conf` → `key` | `core_flow.key` | Different signing keys per session (advanced) |
| Certificate chain | `verifier-service.conf` → `x5c` | `core_flow.x5c` | Different certificate chains per session (x509 flows) |

**Note:**

**Best Practice:** Set defaults in `verifier-service.conf` for your primary use case, and override per-request only when necessary. This keeps your API calls clean and reduces duplication.

See [Verifier Service Configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md) for detailed configuration documentation.

---

## Advanced Options

### Signed Requests

Sign the authorization request JWT using a verifier signing key. This is required for:

- **HAIP profile** compliance
- **DC API Annex C** (ISO 18013-7) flows

Enable by setting `signedRequest: true` in the request body.

**Key Configuration Required:** Signed requests require a signing key (`key`) to be configured. You can either:
- Set the `key` parameter in the  [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional) as a service-wide default
- Override or provide the `key` directly in the verification request body

**Certificate Chain (`x5c`) Required for:**
- X.509-based client authentication when using `x509_san_dns:` or `x509_hash:` as the `clientId` format
- Signed Annex C (ISO 18013-7) requests

The `x5c` parameter can be configured the same way as `key` — as a service-wide default or per-session override.

### Encrypted Responses

The wallet can encrypt the VP token response. Enable by setting `encryptedResponse: true` in the request body.

**Key Configuration Required:** Encrypted responses require a key (`key`) for decrypting the VP token response. Configure it via [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional) or provide it in the verification request body.

### Custom Policies

Beyond signature verification, you can apply additional policies:

```json
"policies": {
  "vc_policies": [
    { "policy": "signature" },
    { "policy": "expired" },
    { "policy": "not-before" },
    { "policy": "schema" }
  ]
}
```

See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for the complete list and configuration options.

---

## Next Steps

- **Apply custom policies** — See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for validation rules beyond signature verification
- **Set up notifications** — Use [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) instead of polling for session status
- **Verify other credential types** — Try [SD-JWT VC](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md) or [mDL](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md)
- **Use DC API** — Try the [Digital Credentials API](https://docs.walt.id/community-stack/verifier2/credential-verification/dc-api.md) for browser-native credential exchange
