Skip to content

Request a Presentation

This is the integration itself: the calls your application makes to ask a holder for a credential, and what it does with the response. It covers steps 2, 4 and 6 of the verification flow — the parts that are yours to build. The wallet exchange in between is handled for you.

Requirements

Start a session

Send a POST to /presentation/oid4vp/new-session. The body has one required field, verification_query_id:

curl -X POST 'https://verifier.example.org/presentation/oid4vp/new-session' \
  -H 'Authorization: Bearer <your API key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "verification_query_id": "b841f44a-d09a-4605-9ef9-b5d99a987ef7"
  }'

This request carries no personal information about the holder. It tells the service to prepare for an interaction, nothing more.

Field Required What it does
verification_query_id Yes The verification query stating which credentials and claims to ask for.
transaction_data No Transaction data objects the holder must authorize as part of the presentation. Omit the field entirely — do not send an empty array — for a presentation that authorizes nothing beyond the verifier's identity.

A missing or unknown verification_query_id returns 400 Bad Request.

Read the response

{
    "session_id": "14eb99d399dda3264a0ad85b93b26eab5396ce17e50056968c2749049ebf6ec1",
    "request_uri": "haip-vp://?request_uri=https%3A%2F%2Fverifier.example.org%2Fpresentation%2Foid4vp%2Fauthorization-request%2F14eb99d3...&client_id=x509_hash%3AVA7rWJULU4O_gDap9PCzgpjMyo8-R3dz7zaU-MRkaBs"
}
  • request_uri is what the wallet needs. Get it in front of the holder.

    It is a deep link, not a web address: the scheme opens the wallet, the nested URL is where it fetches the signed request from, and client_id identifies you by a hash of your signing certificate. Pass the whole value through unchanged — do not unwrap it, and do not build it yourself.

  • session_id identifies this verification for the rest of its life. Keep it: it appears in the result pushed to you, and it is what you pass to the status endpoint.

A verification query is reusable. Create it once, then start a session per interaction.

Get the request URI to the wallet

How you do this depends on where the holder is:

  • On a desktop browser, render the request_uri as a QR code for the holder to scan with their phone.
  • On the same device as the wallet, present it as a link so tapping it opens the wallet directly.

The scheme of the URI determines which app opens it, and that scheme comes from uri_prefix on the verification query — it defaults to haip-vp://. Set it to match the wallets you are targeting; see Verification queries.

The wallet has to reach the service

The request_uri is built from the verifier's baseUrl. A phone cannot resolve localhost, so for a real wallet to complete the flow baseUrl must be an address that phone can reach. This is the single most common reason a QR code scans and then nothing happens.

What happens next, without you

The wallet fetches the authorization request, validates that it comes from a verifier it trusts, shows the holder which claims are being requested, and on approval submits a presentation. The verification service checks the signatures on the presentation and on the credential inside it.

You do not implement any of this. If you want to see what goes over the wire, it is in OID4VP presentation protocol.

Receive the result

When the session completes, the verification service POSTs the result to the sessionCallback.targetUrl you configured, with your bearerToken in the Authorization header:

curl -X POST 'https://example-application.com/verified-claims' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <the token you configured>' \
  -d '{
    "session_id": "14eb99d399dda3264a0ad85b93b26eab5396ce17e50056968c2749049ebf6ec1",
    "verification_result": true,
    "claims": {
      "studentId": 200014762,
      "dateOfBirth": "29-10-1969",
      "enrollmentYear": "01-08-1988"
    }
  }'

Match it to the interaction you started using session_id. Setting this up — the endpoint, the token, and how claim keys are named — is covered in Receive verified data.

What a successful result covers

See Receive verified data for exactly what verification_result: true does and does not tell you.

Checking on a session

If you need to know where a session stands — a holder who has walked away, a timeout in your own UI — poll:

curl 'https://verifier.example.org/presentation/oid4vp/status/14eb99d3...' \
  -H 'Authorization: Bearer <your API key>'
{
    "status": "VERIFICATION_SUCCEEDED",
    "error_message": null
}

status is VERIFICATION_STARTED, VERIFICATION_SUCCEEDED or VERIFICATION_FAILED. On a failure, error_message says what went wrong — read it before anything else.

Use the callback as your primary path and this endpoint for reconciliation. Polling as the main mechanism means holding a session open in your own code for as long as the holder takes.

Presentations without a session

For a request that is not tied to one interaction — a poster in a reception area, a fixed terminal — you can create a static presentation with POST /presentation/oid4vp/static-presentation. It returns a static_presentation_id and a request_uri that stays valid until you withdraw it, so the same QR code serves every holder. See the verifier API reference.

What's Next