Skip to main content

Sessions

A session is EUDIPLO's record of one issuance or presentation flow. It ties the wallet's requests to the offer or request your backend created, stores the result, and enforces that every one-time value is used once. This page explains the rules that apply to both flows.

States​

StatusIssuancePresentation
activeOffer createdRequest created
fetchedFirst credential issuedWallet fetched the request object
completedWallet reported credential_acceptedResponse verified
failedWallet reported credential_failure or credential_deletedVerification failed, or the wallet sent an error
expiredOffer not redeemed before expiresAtNo response before expiresAt

The flow diagrams are on Issuance under the hood and Presentation under the hood.

  • Terminal states are final. completed, failed and expired never change again. Redeeming the offer of a finished session, or answering its presentation request, is rejected and does not overwrite the result.
  • Expiry is checked when the wallet arrives. A presentation request expires after the configuration's lifeTime (default 300 seconds). An offer expires after offerLifetimeSeconds of the offer or the issuance configuration, and never if neither is set. The wallet-facing endpoints that redeem an offer (offer retrieval, PAR, authorization, token) or serve and answer a presentation request compare expiresAt with the current time, so an overdue session is rejected immediately (invalid_grant at the token endpoint, HTTP 400 or 404 elsewhere). The maintenance job only records the expired status afterwards.
  • Redeemed offers do not expire. Once the token exchange succeeded, the wallet can keep requesting credentials with its tokens; expiresAt only limits redemption.
  • Results are structured. A failed presentation stores a machine-readable failure code and an outcome with per-credential details (Session outcome).
  • Changes are observable. Each status change is published on the event stream GET /api/session/{id}/events, which starts with the current status and ends after a terminal one, and in the sessions metric (Receive results, Monitoring).

Single use​

Each one-time value is consumed with one conditional database update, so two concurrent requests cannot both succeed, even across backend instances.

ValueConsumed whenSecond use gets
Credential offer fetched by referenceFirst retrieval (unless ISSUER_MULTI_CONSUMPTION)HTTP 404
Pushed request_uriFirst authorization requestinvalid_request_uri
Authorization code or pre-authorized codeFirst successful token request (the built-in server marks the session consumed)invalid_grant
Credential proof nonceFirst credential request that uses itinvalid_nonce
DPoP proof (jti)First request that uses itinvalid_dpop_proof or invalid_token
Ready deferred credentialFirst retrieval, with a token of the same sessioninvalid_transaction_id
OID4VP or ISO 18013-7 responseFirst response that completes or fails the sessionHTTP 400 "The presentation offer has already been used"

A pre-authorized code is also only valid until the session's creation time plus the tenant's session TTL, and a transaction code (tx_code) locks the code after too many wrong attempts (Credential offers).

Session binding (OID4VP Β§13.3)​

A presentation session has two identifiers, because the QR code is visible to anyone near the screen:

  • The session ID is for your backend: the management API, webhooks and the event stream use it. Whoever has it can read the result.
  • The walletNonce is for the wallet: it appears in request_uri, response_uri and as state. Seeing the QR code reveals only the walletNonce, not the session ID.

Two more values bind the response to the request:

  • nonce is a random value in the request object. The wallet signs it into its key binding JWT or mdoc device authentication, so a presentation captured from another session cannot be replayed.
  • response_code protects same-device flows. After a verified response, EUDIPLO creates a random response_code, stores it as responseCode on the session and appends it to the redirect URI the wallet opens. Your page passes it to your backend, which compares it with responseCode from GET /api/session/{id} before it accepts the result for this browser.

Without the response_code, an attacker could start a request at your site, send its link to a victim, and pick up the victim's verified result in the attacker's own browser session. With it, only the browser that the victim's wallet redirected can claim the result, and a session completes once, so it has exactly one code.

ISO 18013-7 responses are posted by your own page with the session ID, so these values do not apply there. How to implement the check is described in Receive results; the specification text is in OID4VP Β§13.3.

Finding sessions​

GET /api/session lists the sessions of the caller's tenant, most recently updated first. All filters are optional, combined with AND, and always limited to the tenant and to the session types the client's roles allow (Tenants and access).

ParameterMatches
createdFrom, createdToCreation time, bounds included. ISO 8601 with a time zone, for example 2026-10-02T08:00:00Z.
updatedFrom, updatedToTime of the last change, for example sessions that completed or failed in the last hour.
statusOne or more states; repeat the parameter, for example status=active&status=fetched for pending sessions.
typeissuance or presentation.
credentialConfigurationIdIssuance sessions that offer this credential configuration.
requestIdPresentation sessions of this presentation configuration.
failureCodeFailed sessions with this failure code, for example trust_chain_not_trusted.
idSessions whose ID starts with the value.
qSearch, see below.
sortBy, sortOrderid, status, createdAt, updatedAt (default) or requestId; asc or desc (default).

A range that starts after it ends, an unknown status or an ID longer than 255 characters answers 400.

GET /api/session?type=presentation&status=failed&createdFrom=2026-10-02T08:00:00Z&requestId=age-check&sortBy=updatedAt&sortOrder=desc

credentialConfigurationId only finds issuance sessions created with 9.x or later releases that store the offered configuration IDs next to the encrypted offer; older sessions are not backfilled.

q takes whatever identifier you have at hand, for example from a log line, a support request or a screenshot of a QR code:

  • a session ID or its beginning,
  • the walletNonce of a presentation,
  • a pre-authorized code,
  • the reference set when the offer or request was created,
  • a pasted link: a credential offer link (by reference or by value) finds its session, an OID4VP request link (openid4vp://?…request_uri=…) finds the session of its walletNonce.

The search term can contain a pre-authorized code, so its value is replaced by [redacted] in all log lines and in the url.query attribute of traces. The web client offers the same filters in the session list and keeps them in the URL, so a filtered view can be bookmarked or shared; its update-time presets (for example "Last hour") stay relative in a bookmark.

Your own reference​

POST /api/issuer/offer and POST /api/verifier/offer accept an optional reference, an identifier of your system such as an order or case ID (up to 255 characters). It is returned in the session list and detail, sent in the session's webhooks and found by q. The reference is stored in plaintext and stays when sessions are anonymized: never put personal data into it.

From a log line to the session​

Once a wallet request has resolved its session (by offer ID, walletNonce, issuer_state, code or access token), every following log line of that request carries sessionId and tenantId, with or without OpenTelemetry, and the request's trace gets the session.id attribute. See Logging.

Session cleanup​

Sessions contain personal data: claims, offers, authorization requests and presented credentials. Sensitive fields are encrypted at rest (Security model), and sessions are kept only for a retention period:

  • A maintenance job runs every SESSION_TIDY_UP_INTERVAL seconds (default one hour). It marks overdue sessions as expired, then processes every session older than the tenant's TTL, counted from creation (default SESSION_TTL, 24 hours).
  • In full mode (default) the session is deleted.
  • In anonymize mode the session keeps its status, timestamps and protocol metadata, but credentials, credentialPayload, auth_queries, offer, requestObject and responseEncryptionPrivateJwk are cleared. The plaintext reference and credentialConfigurationIds are kept.

The TTL also limits how long a pre-authorized code is valid. The global defaults are SESSION_TTL and SESSION_CLEANUP_MODE (environment variables); a tenant can override both through /api/session-config. Session log entries are stored separately and controlled by LOG_SESSION_STORE (Logging).