Skip to main content

Credential Presentation

WalletSdk.present runs the complete OpenID4VP presentation flow: match stored credentials against the verifier's presentation definition, walk the user through credential and disclosure selection, build the SD-JWT verifiable presentation with holder binding, and submit it to the verifier.

Requires a promptHandler in WalletSdkOptions. Presentation is always available after WalletSdk.initialize.

Flow​

Two ways to run the flow​

presentation.start(...) resolves the request (or reuses prepared state from a deep link) and returns a PresentationSession; session.present() drives the prompts and submission:

final presentation = sdk.presentation!;

final session = await presentation.start(
StartPresentationRequest(
walletExternalId: apiWalletId,
presentationOfferUrl: prepared.resolvedPresentationRequest,
verifier: verifierParty,
prepared: prepared, // from HandleLinkPresentation — skips re-resolve
did: holderDid,
originalPresentationUrl: linkResult.originalPresentationUrl,
verifierMemberId: linkResult.resolvedVerifierMemberId,
),
);

final result = await session.present();

Option B — Direct present()​

Build a PresentRequest yourself, typically from a PreparedPresentation:

final result = await sdk.present(
PresentRequest(
walletExternalId: apiWalletId,
resolvedPresentationRequest: prepared.resolvedPresentationRequest,
presentationDefinition: prepared.presentationDefinition,
verifier: verifierParty,
did: holderDid,
purpose: prepared.purpose,
responseUri: prepared.responseUri.isNotEmpty ? prepared.responseUri : null,
verifierTrustCheckResult: prepared.verifierTrustCheckResult,
),
);

Input — PresentRequest​

FieldTypeRequiredDescription
walletExternalIdStringYesWallet whose credentials are matched
resolvedPresentationRequestStringYesBackend-normalized presentation request
presentationDefinitionMap<String, dynamic>YesParsed presentation definition JSON
verifierPromptPartyYesVerifier identity for prompts (name, branding, trust level)
didString?NoHolder DID for binding
purposeString?NoVerifier-stated purpose shown to the user
recipientDisplayNameString?NoDisplay name recorded in the sharing log
responseUriString?NoVerifier callback for OpenID4VP rejection (error=access_denied)
verifierMemberIdString?NoTrust-network member id for verifier trust gating
verifierTrustCheckResultVerifierTrustCheckResult?NoPrecomputed trust verdict — skips a re-check
preselectedCredentialIdString?NoSkips selection prompts and shares all requested fields (automated flows)

Output — PresentResult​

switch (result) {
case PresentSuccess(:final sharedFieldLabels, :final recipientDisplayName):
showSuccess('Shared ${sharedFieldLabels.length} fields '
'with $recipientDisplayName');
case PresentDeclined():
break; // user rejected the request (confirmed via ConfirmRejectionPrompt)
case PresentAddCredentialNeeded():
openIssuanceFlow(); // user chose "add credential" — nothing eligible
case PresentFailure(:final message):
showError(message);
}

On success the SDK also records a sharing log entry.

Prompts raised during presentation​

Your prompt handler must cover these (see Prompts for full type documentation):

PromptWhenResponses
SelectCredentialPromptCandidate credentials existCredentialChosen, AddCredentialRequested, RejectRequested
SelectDisclosuresPromptSD-JWT flow — choose fields to revealDisclosuresSelected, DisclosureSelectionCancelled
ConfirmPresentationPromptFinal consent (non-selective path)PresentationConfirmed, PresentationConfirmationCancelled
VerifierTrustPromptVerifier untrusted in warn modeProceedDespiteUntrustedVerifier, AbortUntrustedVerifier
ConfirmRejectionPromptUser chose to rejectRejectionConfirmed, RejectionCancelled
RecoverableErrorPromptRetryable failureRetryRequested, FlowAborted

Matching and inspection utilities​

sdk.presentation exposes lower-level helpers for building custom pre-flight UI:

final presentation = sdk.presentation!;

// Credentials that satisfy the definition.
final matches = await presentation.matchCredentials(
walletExternalId: apiWalletId,
presentationDefinition: definition,
);

// Per-credential evaluation with reasons (includes non-matching ones).
final candidates = await presentation.evaluateCandidates(
walletExternalId: apiWalletId,
presentationDefinition: definition,
);
for (final candidate in candidates) {
print('${candidate.credential.id}: satisfies=${candidate.satisfiesRequest} '
'missing=${candidate.missingRequiredFieldPaths}');
}

// Requested fields, purpose, and credential types.
final fields = presentation.extractFields(definition); // List<PresentationField>
final purpose = presentation.extractPurpose(definition); // String?
final types = presentation.extractRawCredentialTypes(definition);

// Whether the request uses SD-JWT selective disclosure.
final isSd = presentation.isSelectiveDisclosure(
presentationDefinition: definition,
);

// Read a displayable value for a requested field from a credential.
final json = presentation.credentialJsonForPresentation(credential);
final value = presentation.readFieldValue(
fieldKey: fields.first.fieldKey,
fields: fields,
credentialJson: json,
);

Additional helpers:

MethodPurpose
prepare(PreparePresentationInput)Resolve a presentation request into a PreparedPresentation without starting prompts
extractFieldsForDescriptorId(definition, id)Fields for one input descriptor
detectFieldConflicts(...)Detect requested fields whose values differ across stored credentials
fetchJarRequestObject(requestUri)Fetch and parse a JAR Request Object JWT (RFC 9101)

PreparedPresentation​

Produced by prepare/start and carried on HandleLinkPresentation:

FieldDescription
resolvedPresentationRequestNormalized request string for present
presentationDefinitionParsed definition JSON
verifierDisplayNameResolved verifier name
purposeVerifier-stated purpose, when present
isSelectiveDisclosureWhether the SD-JWT flow applies
responseUriVerifier response endpoint
verifierTrustCheckResultTrust verdict computed during preparation

prepared.toPresentRequest(...) converts it into a ready-to-submit PresentRequest.

Verifier trust behavior​

With a TrustNetworkMode of warn, an untrusted verifier triggers a VerifierTrustPrompt so the user decides; in strict mode the flow aborts without a prompt. Pass verifierTrustCheckResult from a PreparedPresentation to avoid duplicate checks.