> For the complete documentation index, see [llms.txt](https://docs.empe.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.empe.io/develop/verifier/client-configuration.md).

# Client Configuration

This page explains how to integrate the Verifier into a backend application by calling its REST API directly. The Verifier exposes endpoints for registering a verifier, creating OpenID4VP authorization requests, and tracking the resulting verification sessions. Your server authenticates with an OIDC bearer token and talks to the API over HTTPS — no client library is required.

The examples below use TypeScript with `fetch` running inside an Express server, but the same calls work from any HTTP client.

***

## Prerequisites

Before issuing any verification request, make sure you have the following:

* **The Verifier base URL** — the HTTPS origin where the Verifier API is served (for example `https://your-verifier.evdi.app`). All paths below are relative to this origin.
* **An OIDC access token** — every non-public endpoint requires a valid JWT passed as `Authorization: Bearer <token>`. The token is validated against the configured JWKS endpoint of your OIDC provider (such as Keycloak). The `/agent/*` routes used to register a verifier additionally require the `admin` role; the authorization-request and verification-session routes require only a valid token.
* **A `verifierId`** — the identifier of a verifier record you create once (see below) and then reuse for all authorization requests.

For local development without tokens, see the development bypass in [Authentication](/develop/verifier/authentication.md#development-mode).

### Obtaining an access token

Request a token from your OIDC provider using the client-credentials grant (or any flow that yields a token carrying the `admin` role), then attach it to every call:

```typescript
async function getAccessToken(): Promise<string> {
  const res = await fetch('https://auth.example.com/realms/empe/protocol/openid-connect/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: 'your-client-id',
      client_secret: 'your-oidc-client-credentials-secret',
    }),
  });

  const data = await res.json();
  return data.access_token;
}
```

> For a **One-Click deployment**, obtain the token from your realm's Keycloak token endpoint — `https://<keycloak-host>/realms/<realm>/protocol/openid-connect/token` — using the credentials shown when you created the deployment; see [Authenticating to Your Deployment](/develop/intro/authenticating-to-your-deployment.md). If you run your **own identity provider**, use its token endpoint and a client you control (as shown above).

***

## Registering a Verifier

A verifier record is created once and reused. Create it with the Agent API.

**`POST /agent/verifier`**

### Request Body

* **`verifierId`** (string, optional) A custom identifier for tracing (for example `verifier-empe`). If you omit it, the service assigns one. You do not need to supply a DID here; the signing DID is chosen later when you create authorization requests.

```typescript
const VERIFIER_BASE_URL = 'https://your-verifier.evdi.app';

async function createVerifier(token: string): Promise<string> {
  const res = await fetch(`${VERIFIER_BASE_URL}/agent/verifier`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({ verifierId: 'verifier-empe' }),
  });

  const data = await res.json();
  return data.verifierId;
}
```

### Response Body

* **`verifierId`** (string) The identifier to use in subsequent authorization-request paths.
* **`clientMetadata`** (object, optional) OpenID4VP client metadata attached to the verifier.

Persist the returned `verifierId` in your configuration; you will reference it in every authorization request.

***

## Creating an Authorization Request

To ask a wallet to present credentials, create an authorization request under your verifier. The request describes which credentials and claims you want, using either a **DCQL query** or a **DIF Presentation Exchange v2** definition.

**`POST /verifiers/{verifierId}/authorization-requests`**

### Request Body

Exactly one of `dcqlQuery` or `presentationDefinition` is required; the remaining fields are optional.

* **`dcqlQuery`** (object) — a DCQL query describing the requested credential configurations and claims. Required if `presentationDefinition` is not provided.
* **`presentationDefinition`** (object) — a DIF Presentation Exchange v2 definition. Required if `dcqlQuery` is not provided. Can only be used with `version` `v1.draft21` or `v1.draft24`.
* **`responseMode`** (string, optional) — one of `direct_post`, `direct_post.jwt`, `dc_api`, `dc_api.jwt`. Defaults to `direct_post.jwt`.
* **`version`** (string, optional) — OpenID4VP draft version: `v1`, `v1.draft21`, or `v1.draft24`. Defaults to `v1`, which supports only `dcqlQuery`.
* **`signingDid`** (string, optional) — the DID that signs the authorization request. If omitted, the `verifierId` in the path must itself be a DID (for example a `did:web` or `did:key` registered with the Agent API).
* **`authorizationResponseRedirectUri`** (string, optional) — a redirect URI to include in authorization responses.
* **`expectedOrigins`** (string array, optional) — expected origins for Digital Credentials API flows.
* **`transactionData`** (object array, optional) — transaction data entries to include in the request.
* **`verifierInfo`** (object array, optional) — verifier attestations to include in the request.

The following example requests an `EmployeeBadge` SD-JWT credential and asks the wallet to disclose only the `employee_id` and `department` claims:

```typescript
async function createAuthorizationRequest(token: string, verifierId: string) {
  const res = await fetch(
    `${VERIFIER_BASE_URL}/verifiers/${verifierId}/authorization-requests`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token}`,
      },
      body: JSON.stringify({
        responseMode: 'direct_post.jwt',
        version: 'v1',
        signingDid: 'did:web:verifier.example.com:1f8a6a8b',
        dcqlQuery: {
          credentials: [
            {
              id: 'employee_badge',
              format: 'dc+sd-jwt',
              meta: {
                vct_values: ['https://issuer.example.com/vct/employee-badge'],
              },
              claims: [{ path: ['employee_id'] }, { path: ['department'] }],
            },
          ],
        },
      }),
    },
  );

  return res.json();
}
```

### Response Body

* **`authorizationRequestUri`** (string) — an `openid4vp://...` URI. Encode it into a QR code or use it as a deep link so a wallet can fetch and respond to the request.
* **`verificationSessionId`** (string) — the session identifier you poll or subscribe to in order to learn the outcome.
* **`authorizationRequestId`** (string, optional) — the identifier of the hosted request.
* **`expiresAt`** (string, optional) — ISO timestamp after which the request is no longer valid.

```json
{
  "authorizationRequestUri": "openid4vp://?request_uri=https://your-verifier.evdi.app/...",
  "verificationSessionId": "18b760b1-4ab8-4d38-9d42-6b4c25a4d2e0",
  "authorizationRequestId": "1b8a6a8b-2ac1-4f0b-9d9c-6b9c859b1af2",
  "expiresAt": "2026-06-22T12:34:56.789Z"
}
```

If you would rather receive a ready-to-render QR image, send the same body to **`POST /verifiers/{verifierId}/authorization-requests/qr-code`**. It returns a PNG and exposes the same values through the `X-Authorization-Request-Uri`, `X-Verification-Session-Id`, and `X-Authorization-Request-Id` response headers.

***

## Reacting to Results

The response from a wallet is processed asynchronously, so your server watches the verification session and applies its own logic (issuing a login token, completing a checkout, granting access) once the session succeeds. There is no callback framework: you own this logic.

A session moves through these states:

* **`RequestCreated`** — the request exists and is waiting for a wallet.
* **`RequestUriRetrieved`** — a wallet has fetched the request.
* **`ResponseVerified`** — the wallet returned a valid presentation. This is the success state.
* **`Error`** — verification failed; inspect `errorMessage`.

### Polling the session

**`GET /verification-sessions/{sessionId}`**

```typescript
async function getSession(token: string, sessionId: string) {
  const res = await fetch(`${VERIFIER_BASE_URL}/verification-sessions/${sessionId}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  return res.json();
}
```

The response includes the session `state`, the `verifierId`, the authorization request URI, `expiresAt`, and an `errorMessage` when something went wrong:

```json
{
  "id": "18b760b1-4ab8-4d38-9d42-6b4c25a4d2e0",
  "verifierId": "verifier-empe",
  "state": "ResponseVerified",
  "authorizationRequestId": "1b8a6a8b-2ac1-4f0b-9d9c-6b9c859b1af2",
  "authorizationRequestUri": "openid4vp://?request_uri=...",
  "expiresAt": "2026-06-22T12:34:56.789Z"
}
```

When `state` reaches `ResponseVerified`, run your post-verification logic:

```typescript
const session = await getSession(token, sessionId);

if (session.state === 'ResponseVerified') {
  // The wallet presented a valid credential. Apply your own logic here, e.g.
  // mint a session/login token, mark the user authenticated, redirect, etc.
} else if (session.state === 'Error') {
  // Verification failed.
  console.error('Verification failed:', session.errorMessage);
}
```

### Subscribing with Server-Sent Events

To avoid polling, subscribe to live updates instead.

**`GET /verification-sessions/{sessionId}/events`**

This endpoint streams Server-Sent Events. It emits a `session` event carrying the full session object immediately on subscribe (reflecting the current state) and again on every state change, plus a `ping` heartbeat every 15 seconds to keep the connection alive. Close the stream once you observe a terminal state (`ResponseVerified` or `Error`).

```typescript
import { EventSource } from 'eventsource';

const es = new EventSource(
  `${VERIFIER_BASE_URL}/verification-sessions/${sessionId}/events`,
  { fetch: (url, init) => fetch(url, { ...init, headers: { ...init.headers, Authorization: `Bearer ${token}` } }) },
);

es.addEventListener('session', (event) => {
  const session = JSON.parse(event.data);
  if (session.state === 'ResponseVerified') {
    // Apply your post-verification logic, then close the stream.
    es.close();
  } else if (session.state === 'Error') {
    console.error('Verification failed:', session.errorMessage);
    es.close();
  }
});
```

This pattern lets your backend react the moment a wallet completes the presentation, without holding open long-running poll loops.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.empe.io/develop/verifier/client-configuration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
