> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hopae.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Evidence & Data Integrity

> How Hopae Connect lets you independently prove that returned identity data came from the source provider, untampered.

Hopae Connect is a **gateway**: every verification result travels from the eID provider, through Hopae, to your application. **Evidence** is how you prove that what you received is exactly what the provider issued, and that nothing was altered in transit.

When it is available, each credential in a result carries an `evidence` object under [`provenance`](/v2/guides/verifications/return-data-model#what-provenance-means). It preserves the **signed proof material** behind the verification so you (or an auditor) can verify the result against the signer's own keys, rather than taking Hopae's word for it.

## Why evidence exists

Because Hopae sits in the middle, a sceptical RP could ask: *how do I know Hopae returned the provider's data faithfully, and didn't change a name or a birthdate along the way?* Evidence answers that. A cryptographic signature travels end-to-end, so the proof you verify was produced by the signer, not fabricated by the gateway.

```mermaid theme={null}
sequenceDiagram
  participant eID as eID Provider (issuer)
  participant Hopae as Hopae Connect (gateway)
  participant App as Your App (RP)

  eID->>Hopae: Verified claims + signed assertion
  Note over Hopae: Relays the signature untouched
  Hopae->>App: Normalized claims + evidence (signed assertion)
  Note over App: Verify the signature against the signer's keys
  App->>App: Confirm data is authentic & unaltered
```

**What you can establish depends on the artifact:**

* A **signed assertion** (JWS, SAML, COSE, passport SOD, detached signature) gives you **integrity**, **authenticity**, and **non-repudiation** against the signer's keys.
* **Integrator evidence** (see below) proves Hopae received and attests to exactly this data: integrity and non-repudiation *from Hopae*, for providers that issue no signature of their own.
* **Opaque companions** (such as a Bearer `access_token`) and unsigned fields carry no independent proof. Don't treat them as verification material.
* **Wallet presentations** (mdoc, Verifiable Presentation) carry their own proofs that *you* validate against the issuer's trust chain.

## Where to find it

Evidence appears per credential, alongside the source's `claims`:

```jsonc theme={null}
provenance.presentation.credentials[].evidence   // one per credential
```

Read it from the userinfo response, either [OIDC](/v2/api-reference/oidc/userinfo) or [REST](/v2/api-reference/verifications/get-verification-userinfo). There is no separate evidence endpoint in v2. Every credential's evidence travels inside `provenance`.

## Two kinds of token evidence

Not all eID providers issue a signed assertion. So evidence comes from one of two sources. **Detect them by key presence**, not by a single field:

* **Integrator**: `evidence.token.integrator_jws` is present (with `token_type: "integrator"`).
* **Pass-through**: anything else with a `token` object: the provider's own artifacts.

| | Pass-through | Integrator |
| - | - | - |
| **What it is** | The issuer's **original** signed artifact, relayed untouched | A JWS **Hopae signs** over the provider's claims |
| **Detect by** | a `token` object **without** `integrator_jws` | `evidence.token.integrator_jws` present |
| **Who signed it** | the source provider / issuing authority | Hopae |
| **Verify against** | the **signer's** keys or certificate | **Hopae's OIDC JWKS** |
| **Used when** | the provider returns its own signed assertion | the provider issues none (and signing is configured) |

<Note>
  A `token` object can also hold non-proof material, e.g. an opaque `access_token` with `token_type: "Bearer"`. Verify the **signed artifact** (a JWS, SAML assertion, COSE signature, or SOD), not its opaque companions.
</Note>

### Pass-through evidence

The provider's signed artifact is relayed **as-is**. Hopae never re-signs it. (Token-string artifacts like JWS and SAML pass through unchanged. Wallet artifacts may be re-encoded for transport, e.g. CBOR→base64, but the issuer signature is preserved.) Common signed forms:

* **`id_token` / `userinfo_jws`**: a JWS signed by the provider.
* **`saml_response`**: a signed SAML assertion (base64).
* **`signature` + `certificate` (+ `signed_hash`)**: a detached signature with the signer's certificate.
* **`sod` + data groups (`dg1`…`dg14`)**: a passport's Document Security Object (ICAO 9303 passive authentication).
* **`signed_pdf` (+ `signed_hash`)**: a signed PDF document.
* **`issuerAuth`**: an ISO 18013-5 mdoc COSE signature (see [Wallet evidence](#wallet-evidence-mdoc--vp)).

Verifying these requires the **signer's** public keys or certificate. Discovery is provider-specific (an OIDC JWKS, SAML metadata certificate, or an ICAO CSCA/Document-Signer chain) and is real cryptographic work. The signer is the issuer behind the connection named by the response's `provenance._metadata.provider_id` and `credential_id`. The connection's details in the Console tell you which authority that is.

### Integrator evidence

When a provider does not return a signed assertion of its own, Hopae signs the **provider's claims** with its own OIDC signing key (RS256), so the payload is still integrity-protected and attributable to Hopae.

```jsonc theme={null}
"evidence": {
  "token": {
    "integrator_jws": "eyJhbGciOiJSUzI1NiIsInR5cCI6...",  // RS256 JWS
    "token_type": "integrator"
  },
  "names": "integrator_jws;token_type"
}
```

The JWS payload carries the provider id and the signed claims, with standard time claims:

```jsonc theme={null}
{
  "iss": "https://connect.hopae.com",      // Hopae's OIDC issuer
  "iat": 1730352000,
  "exp": 1730438400,                     // ~24h after issuance, enforced on verify
  "providerId": "pass",
  "data": { /* the provider's claims */ }
}
```

Verify it against Hopae's OIDC keys at `https://connect.hopae.com/jwks`. These are the same keys that sign ID tokens.

```ts theme={null}
import { jwtVerify, createRemoteJWKSet } from 'jose'

const issuer = 'https://connect.hopae.com'
const JWKS = createRemoteJWKSet(new URL(`${issuer}/jwks`))

const { payload } = await jwtVerify(evidence.token.integrator_jws, JWKS, { issuer })
// payload.data holds the provider's claims, integrity-protected by Hopae.
// jwtVerify enforces `exp`: integrator evidence expires ~24h after issuance, so
// verify it promptly or persist the verified result.
```

The correct key is selected automatically by the JWS `kid`.

## Wallet evidence (mdoc / VP)

Wallet credentials do **not** use the `{ token, names }` shape. They place their fields directly on `evidence`, and you validate their proofs yourself against the issuer's trust chain:

| Family | Fields on `evidence` | `token_type` | You verify by |
| - | - | - | - |
| mdoc (ISO 18013-5) | `docType`, `issuerAuth`, `nameSpaces`, `deviceAuth?` | `iso_18013_5_mdoc` | validating the `issuerAuth` COSE signature (MSO) against the issuer/IACA certificate and checking the MSO digests against `nameSpaces` |
| Verifiable Presentation | `vpClaims` | (none) | validating the VP proof and issuer trust chain, **only when** `vpClaims` carries the original presentation (some providers store decoded claims only) |

<Warning>
  Wallet evidence is only as strong as the validation **you** perform, and its contents vary by provider. `issuerAuth` may be empty when issuer authentication wasn't captured. `vpClaims` may hold **decoded claims rather than the original signed presentation**. Treat empty or claims-only material as **unverifiable**: independent cryptographic verification is possible only when the evidence carries the original signed artifact. Never infer authenticity from mere presence.
</Warning>

## Reading the evidence object

Detect the shape before reading, because `names` exists only for the token shape:

```ts theme={null}
function readEvidence(evidence) {
  if (!evidence) return null
  const tokenType = evidence.token_type ?? evidence.token?.token_type

  if (evidence.token?.integrator_jws) return { kind: 'integrator', token: evidence.token }
  if (tokenType === 'iso_18013_5_mdoc' || evidence.docType || evidence.nameSpaces)
    return { kind: 'mdoc', evidence }
  if (evidence.vpClaims) return { kind: 'vp', evidence }

  if (evidence.token) {
    // Standard shape: enumerate via `names`, falling back to the token keys
    const keys = evidence.names ? evidence.names.split(';') : Object.keys(evidence.token)
    return { kind: 'token', keys, token: evidence.token }
  }
  return { kind: 'other', evidence }
}
```

## What is relayed vs. set by the gateway

The signed artifact itself is always the signer's (pass-through) or Hopae's (integrator). That is what you verify. A few **convenience fields** around it are added by the gateway and are *not* covered by any signature:

* `expires_at`: derived from the artifact (e.g. the `id_token`'s `exp`).
* Some `token_type` labels (`Bearer`, `icao_9303_lds`, `iso_18013_5_mdoc`, `integrator`): set by Hopae to describe the artifact.

When you verify, verify the **artifact**, not the surrounding helper fields, and not opaque tokens.

## Related

* [Return Data Model](/v2/guides/verifications/return-data-model): the full payload `evidence` sits inside
* [Data Types: Provenance](/v2/guides/data-types/provenance): copy-paste types
* [Get Verification UserInfo](/v2/api-reference/verifications/get-verification-userinfo): the REST response that carries `provenance`
* [OIDC Integration](/v2/guides/oidc-integration): issuer, discovery, and JWKS


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.