Presentation under the hood
This page explains what EUDIPLO does during a presentation: the message flow, what the request object contains and how a response is verified. To set up verification, start with the presentation guides; supported features are listed in Supported protocols.
Flow
POST /api/verifier/offer creates the session and signs the request object right away. The response contains uri for same-device use, crossDeviceUri for a QR code shown on another device, and the session ID your backend uses to read the result. Fetching the request through crossDeviceUri (its path ends in /request/no-redirect) removes the redirect URI, so the wallet does not open the relying party's page on the phone after a cross-device presentation.
The wallet-facing URLs contain a walletNonce, not the session ID; Sessions explains why.
Request object
The request object is a JWT with typ: oauth-authz-req+jwt, signed with ES256 by the presentation configuration's access key chain, whose certificate chain is in the x5c header.
{
"response_type": "vp_token",
"response_mode": "direct_post.jwt",
"client_id": "x509_hash:<hash of the access certificate>",
"response_uri": "https://eudiplo.example.com/presentations/<walletNonce>/oid4vp",
"state": "<walletNonce>",
"nonce": "<random UUID>",
"dcql_query": { "credentials": [{ "id": "membership", "format": "dc+sd-jwt", "meta": { "vct_values": ["urn:example:membership:1"] } }] },
"client_metadata": {
"jwks": { "keys": ["<ephemeral ECDH-ES public key>"] },
"encrypted_response_enc_values_supported": ["A128GCM", "A256GCM"],
"vp_formats_supported": { "dc+sd-jwt": {}, "mso_mdoc": {} }
}
}
What EUDIPLO adds to the configured DCQL query:
client_idis derived from the access certificate:x509_hashby default,x509_san_dnsif the request asks for it.- Response encryption key. Each session gets a fresh P-256 key pair. The public key goes into
client_metadata.jwks; the private key is stored encrypted in the session and deleted when the session ends. nonceis a random value per request; the wallet must bind its presentation to it.- Trusted authorities.
etsi_tlentries in the configuration reference trust lists. In the request they becomeakivalues, the key identifiers of the issuer certificates listed in those trust lists; trust lists whose issuers cannot all be expressed that way are also sent asetsi_tlURLs. See DCQL. - Optional parts:
transaction_data(from the request or the configuration), and a registration certificate inverifier_infoif the configuration requests one.
Delivery variants
| Variant | How the request reaches the wallet | Where the response goes |
|---|---|---|
uri | openid4vp:// link with request_uri, as QR code or deep link | The wallet posts to response_uri |
dc-api | Your page passes the request object to the browser's Digital Credentials API; response_mode is dc_api.jwt and expected_origins holds your page's origin | Your page posts the wallet's encrypted response to response_uri |
iso-18013-7 | device_request and encryption_info for the org-iso-mdoc protocol | Your page posts to POST /presentations/{session}/iso-18013-7 |
The ISO 18013-7 variant uses its own request format (HPKE encryption with the tenant's encrypt key chain, mdoc device authentication) and does not use redirectUri, transaction_data or clientIdScheme. Details are in Presentation requests.
Wallet response
The wallet posts response=<JWE> to the response_uri. EUDIPLO only accepts encrypted responses: it decrypts the JWE with the session's private key and checks that state, if present, equals the walletNonce. The decrypted vp_token is an object keyed by DCQL credential id, each with one or more presentations.
If the user declines or the wallet cannot answer, the wallet sends an OAuth error (error, error_description) instead, plain or encrypted. EUDIPLO marks the session failed with the wallet's error code and answers with HTTP 200.
Verification pipeline
- Completeness. Every required
credential_setsoption must be satisfied; without credential sets, every credential query must be answered. Credential IDs that are not in the query are rejected, and a query that does not setmultiple: trueaccepts only one presentation. - Signature and trust. The format verifier (SD-JWT VC or mdoc) checks the issuer signature, builds the issuer certificate chain and checks every certificate's validity period. Trust is opt-in per credential query: with
trusted_authorities, the chain must match a PID or EAA issuer listed in one of the trust lists, and a trust list that cannot be loaded, has a bad signature or is past its next update fails the check. Withouttrusted_authorities, any validly signed issuer is accepted. Certificate revocation (CRL, OCSP) is not checked.openid_federationauthorities are evaluated as described in OpenID Federation. - Holder binding. The key binding JWT (SD-JWT VC) or device authentication (mdoc) must match the request's
nonceand audience and, with transaction data, contain the matching hashes. - Status. The credential's status list is fetched and checked according to
statusCheckMode:strictfails if the list is unavailable,best_effortcontinues,disabledskips the check. - Claims. The requested claims, or one of the
claim_sets, must be disclosed, each with one of itsvaluesif the claim query lists them (claim_value_mismatchotherwise).
A failed check records the failure as a structured outcome on the session, with a machine-readable code such as trust_chain_not_trusted (Session outcome).
Result
On success, EUDIPLO completes the session in a single conditional update, so a second response for the same request is rejected. It stores the disclosed claims and a one-time response_code, then:
- sends the presentation webhook, whose answer may replace the redirect URI;
- answers the wallet with
redirect_uriplusresponse_codeif a redirect URI is set (from the request, the configuration or the webhook), so the user returns to your page on the same device.
On failure, the webhook reports failed, and the redirect carries error and error_description instead of a response_code. Your backend can also follow the session through polling or the event stream (Receive results).
Session states
A request expires after the configuration's lifeTime (default 300 seconds). The DC API and ISO 18013-7 variants can complete without a fetched step. Rules shared with issuance are on Sessions.