For the complete documentation index, see llms.txt. This page is also available as Markdown.

Flows: Claim and Presentation

A cloud wallet (a holder) runs two end-to-end flows: claiming a credential from an issuer (OpenID4VCI) and presenting credentials to a verifier (OpenID4VP). Both start the same way — you hand the wallet the URI a mobile wallet would scan from a QR code — and both complete in a single call to POST /holders/:holderId/process, which dispatches on the URI scheme.

This page walks through what actually happens inside each flow, step by step. For the endpoint's request/response reference, options, and error catalog, see Processing URIs. A holder is created beforehand via POST /agent/holder; see Holders and Tenancy.

The Claim Flow (Issuance)

An issuer that wants to hand out a credential creates a credential offer and exposes it as an openid-credential-offer://... URI (usually rendered as a QR code or deep link). Claiming it into a cloud wallet runs like this:

  1. Submit the URI. You call POST /holders/:holderId/process with the offer URI. The openid-credential-offer:// scheme routes the call into the issuance flow.

  2. DID check. The wallet verifies the holder has a DID — the identity the credential will be bound to. It uses the holder's first DID and picks a verification method from its DID document. A holder without a DID cannot claim, and the call fails with 400 Bad Request.

  3. Resolve the offer. The wallet fetches the offer from the issuer and reads which credential configurations are being offered.

  4. Request an access token. Using the offer's pre-authorized code, the wallet obtains an access token from the issuer.

  5. Request the credentials. The wallet requests every offered credential configuration, proving control of the holder's DID so the issuer binds each credential to it.

  6. Store. Each returned credential is stored in the wallet's store for its format — SD-JWT VC, JWT-VC-JSON, JSON-LD VC, W3C v2 (vc+jwt / vc+sd-jwt), or mdoc.

The whole sequence runs synchronously inside the one call. When it returns, the flow is over: the response reports how many credentials were stored and their record ids ({ "status": "success", "claimed": 1, "ids": ["..."] }), and on the issuer's side the issuance session moves to Completed. The new credentials are immediately visible through the listing endpoints — see Credential Management.

The Presentation Flow (Verification)

A verifier that wants proof of something creates an authorization request and exposes it as an openid4vp://... (or https://...) URI. Answering it from a cloud wallet runs like this:

  1. Submit the URI. You call the same POST /holders/:holderId/process endpoint with the request URI. The openid4vp:// or https:// scheme routes the call into the presentation flow.

  2. Resolve the request. The wallet fetches the verifier's authorization request and reads its credential query — either DIF Presentation Exchange v2 or DCQL, whichever the request carries.

  3. Select credentials. The wallet matches the query against the holder's stored credentials and selects the ones that satisfy it. Selection is fully automatic; the caller never names a credential. If nothing in the wallet matches, the presentation cannot be built and the call fails.

  4. Build and submit. The wallet builds the presentation (honoring selective disclosure for SD-JWT credentials) and submits it to the verifier's response endpoint.

Again, everything happens inside the single call. The response { "status": "success", "action": "presentation_sent" } means the presentation was delivered; the verifier's verification session then records the result on the verifier's side.

States and Sessions

The wallet side is stateless by design: there is no wallet-side session or flow object to poll, and no initialize/confirm handshake — each process call runs its flow to completion and returns the final result. Protocol state lives with the counterparties: the issuer tracks an issuance session (from offer creation to Completed) and the verifier tracks a verification session for its request. Access control is per holder — a caller can only run these flows for holders they own.

What Can Go Wrong

  • Claiming without a DID fails at step 2 with 400 Bad Request — the holder's DID is created with the holder, so this only happens for misconfigured tenants.

  • Presenting without a matching credential fails at step 3 — claim the required credential first, then retry.

  • An unrecognized URI scheme is rejected with 400 Bad Request before either flow starts.

For the exact error messages, response bodies, and curl examples, see Processing URIs.

Last updated