Skip to content
Last updated

Verification Flow

This guide explains the full lifecycle of an identity verification session — from creation to result retrieval.

Prerequisites

  • An API credential with the deepcredentials.verify scope. See Authentication.
  • A credential definition configured in your organization.

Session lifecycle

POST /verification-sessions

User presents valid credentials

Presentation rejected or invalid

Session TTL exceeded

pending

success

failed

expired

POST /verification-sessions

User presents valid credentials

Presentation rejected or invalid

Session TTL exceeded

pending

success

failed

expired

Statuses

Status    Description
pendingSession created, waiting for user to scan QR and present credentials.
successCredentials verified successfully. credential_subject_data contains the disclosed claims. Check credential_status too — a suspended or revoked credential still reports success here.
failedVerification failed. Check error_code and error_description for details.
expiredSession exceeded its TTL without completion.

Step 1: Create a session

Open in API reference →

curl -i -X POST \
  https://api.dev.deepcredentials.swiss/b2b/v1/verification-sessions \
  -H 'Authorization: Bearer <YOUR_JWT_HERE>' \
  -H 'Content-Type: application/json' \
  -H 'X-Environment: beta' \
  -H 'X-Org-Id: 497f6eca-6276-4993-bfeb-53cbbbba6f08' \
  -d '{
    "allowed_issuer_dids": [
      "string"
    ],
    "case_id": "c74269e5-1f97-4e20-9164-ffbe3494d8d6",
    "claims": [
      "string"
    ],
    "credential_definition_id": "631a1b36-dbe6-4899-b427-3167b0844238",
    "purpose": "string",
    "reference_id": "string"
  }'

Response

session_idstring, (uuid)required

UUID v4 identifier.

Example:"550e8400-e29b-41d4-a716-446655440000"
qr_content_base64stringrequired

Base64-encoded PNG; QR payload is the same URI as deeplink.

deeplinkstringrequired

Wallet app link, e.g. openid4vp://?client_id=...&request_uri=...

statusstring(verification.SessionStatus)required

Lifecycle state of a verification session.

Enum:"pending""success""failed""expired"
expires_atstring, (date-time)required

Session TTL deadline (RFC3339).

Step 2: Present the QR code

You have two options to present the verification request to the user:

QR code (desktop / in-person)

The qr_content_base64 field is a standard Base64-encoded PNG image. Display it directly:

<img src="data:image/png;base64,iVBORw0KGgo..." alt="Scan to verify your identity" />

Or decode it to a file:

import base64

png_bytes = base64.b64decode(qr_content_base64)
with open("qr.png", "wb") as f:
    f.write(png_bytes)

The deeplink field contains a URI that opens the wallet app directly:

swiyu-verify://?client_id=...&request_uri=...

Use this when the user is on a mobile device — redirect them to the deeplink instead of showing a QR code.

Step 3: Retrieve the result

Option A: Polling

Poll GET /b2b/v1/verification-sessions/{sessionId} until the status is no longer pending:

Open in API reference →

curl -i -X GET \
  'https://api.dev.deepcredentials.swiss/b2b/v1/verification-sessions/{sessionId}' \
  -H 'Authorization: Bearer <YOUR_JWT_HERE>' \
  -H 'X-Environment: beta' \
  -H 'X-Org-Id: 497f6eca-6276-4993-bfeb-53cbbbba6f08'

Response

session_idstring, (uuid)required

Session id (matches the value returned at creation time). UUID v4 identifier.

Example:"550e8400-e29b-41d4-a716-446655440000"
statusstring(verification.SessionStatus)required

Current lifecycle state of the session.

Enum:"pending""success""failed""expired"
expires_atstring, (date-time)required

When a pending session expires if not completed (RFC3339).

environmentstringrequired

Swiyu environment: "beta" or "production" (matches the credential binding).

credential_definitionobject(b2b.credentialDefinitionRef)required

Credential definition reference.

requested_fieldsArray of stringsrequired

Claim keys requested in the presentation definition.

credential_subject_typesobjectrequired

For sessions created through the deprecated credential_definition_id plus claims request shape, this maps requested claim names to the types configured on the credential definition. It is currently an empty object for sessions created with case_id. Partners using case-based verification must not rely on this field to infer disclosed-value types.

created_atstring, (date-time)required

When the session was created (RFC3339).

accepted_issuer_didsArray of strings

Effective issuer allow-list for this session (may come from credential definition locks).

completed_atstring, (date-time)

Set when the session leaves pending (success, failed, or expired).

credential_statusstring

On success: the token-status-list state of the credential the holder presented — "valid", "suspended" (shown as "Locked" in the portal), or "revoked". Resolved when the holder presented and never recomputed.

A "suspended" or "revoked" value still accompanies a successful verification with its disclosed claims: the wallet can present such a credential and the holder can consent to sharing it, so the verifier is given the state and applies its own policy. Branch on this in addition to `status` if a locked credential should not be accepted.

credential_subject_dataobject

On success: disclosed subject claims (structure depends on credential definition and wallet).

deeplinkstring

Only when status is pending: wallet deep link (e.g. openid4vp://...).

error_codestring

Machine-readable failure reason when status is failed or expired.

error_descriptionstring

Human-readable error detail when status is failed or expired.

matched_vctstring

On success: the vct of the credential the holder actually presented, relevant when the session accepts more than one credential type.

purposestring

Purpose string from the create request, if any.

qr_content_base64string

Only when status is pending: Base64 PNG QR code for the wallet.

reference_idstring

Your reference_id from the create request, if any.

While status is pending, only session_id, qr_content_base64, deeplink, status, and expires_at are populated. On success, credential_subject_data contains the disclosed claims and qr_content_base64/deeplink are omitted. Sessions created with the deprecated credential_definition_id plus claims shape populate credential_subject_types from the definition. Case-based sessions currently return an empty object instead, so do not use that field to infer types in the preferred flow.

On success, credential_status reports the token-status-list state of the credential the holder actually presented. Values: valid, suspended, or revoked.

A locked or revoked credential still verifies successfully

suspended and revoked arrive with status: "success" and the full credential_subject_data. This is not a bug: a wallet can present a locked or revoked credential and the holder can consent to sharing it, so the presentation genuinely verified — the issuer has simply since withdrawn the credential's validity.

Checking status == "success" alone will accept a withdrawn credential. If your integration must not accept one, branch on credential_status in addition to status.

Note

When status is no longer pending, qr_content_base64 and deeplink are omitted from the response — the wallet flow is complete.

Polling recommendations:

  • Poll every 2–3 seconds.
  • Stop when status is success, failed, or expired.
  • Respect the expires_at timestamp — stop polling after expiration.

Option B: Webhooks

For production integrations, opt in to the Verification Completed webhook event. Its data field is a slim terminal hint containing session_id, status, and, on successful status resolution, credential_status. Follow it with authenticated GET /b2b/v1/verification-sessions/{sessionId} to reconcile the authoritative session and read disclosed fields. The callback is not a copy of the GET response.

See the Webhooks guide for setup, signature verification, and secret rotation.

Error handling

On failure: the same response object is returned with status: failed and error_code/error_description populated. See the full schema above.

On a status-check failure: if the credential's token-status-list entry can't be fetched or its signature can't be verified, the session fails with error_code: status_check_failed rather than being reported with an unknown or stale state. credential_status is absent on such a session.

Status-check failures can originate from issuer infrastructure

A status_check_failed rejection may result from problems with the issuer's status-list infrastructure, not from a cryptographically invalid credential. For example, the issuer rotates its signing key and republishes the status list under the new key — credentials issued before the rotation reference the old key and can no longer have their list verified — or the issuer issues credentials with no status reference at all. In both cases, a valid credential is rejected and your session returns no claims. This is especially relevant when verifying credentials from issuers you do not operate.

On expiration:

Sessions that remain pending past their expires_at transition to expired automatically.

See Error Handling for a complete list of error codes.

Next steps

  • Webhooks — Receive verification results in real time.
  • Error Handling — Handle all possible error scenarios.