Skip to main content

Verification Integration Guide

This guide covers a production-grade integration with Verification Gateway: creating verification sessions, presenting the QR code, interpreting results and failure reasons, and making safe access decisions.

If you haven't made your first call yet, start with Your First API Call (Verification Gateway).

Integration overview​

Step 1 — Create the session​

POST /api/v1/verifier/sessions with an OAuth Bearer token (full reference). Every field is optional — {} uses your configuration's defaults. In production, always set externalId:

{
"credentialType": "VerifiedPerson",
"presentationDefinitionId": "verified-person-v1",
"externalId": "kyc-check-99887",
"ttlSeconds": 120
}
  • credentialType and presentationDefinitionId select what you're asking for; omit them to use your configuration's defaults.
  • ttlSeconds defaults to your configuration's default, or 60 when it sets none. A value below 5 or above your configuration's maximum (300 when it sets none) returns 400 (invalid-input); Verification Gateway does not shorten it for you. A live, attended flow rarely needs more than 120–300 seconds.

Step 2 — Present the QR code​

Render qrCodeDataUrl in an <img>, or render authorizationUrl yourself (QR Code Rendering). On mobile, present authorizationUrl as a tappable openid4vp:// deep link.

Understanding client_metadata vs. client_id​

The authorizationUrl contains two distinct identity parameters — a common source of confusion:

ParameterLocationPurpose
client_idQuery param in authorizationUrlInternal identifier used for KB-JWT audience validation. Not for display or trust lookup.
verifier_member_idInside the client_metadata query param (JSON, URL-encoded)Your verifier's identity in the iDen2 Trust Network. Holder wallets use it to look up and display your organisation on the consent screen.

Your system does not need to parse or reconstruct the authorizationUrl — just pass it to the QR renderer. This distinction matters only if you're building a custom wallet integration or debugging trust network issues.

Step 3 — Poll for the result​

Poll GET /api/v1/verifier/sessions/{sessionId} every 3–5 seconds until terminal, stopping at expiresAt:

StatusTerminal?Meaning
pendingNoWaiting for the holder
completedYesPresentation received and verification passed
failedYesCheck failureReason

Step 4 — Interpret a completed session​

A completed session gives you two things:

  • credentialData — the claims the holder disclosed (e.g. givenName, dateOfBirth). Only the fields covered by the presentation definition and the holder's consent are present.
  • verificationResult — the evidence. Before acting on credentialData, confirm:
const ok =
session.status === 'completed' &&
session.verificationResult.signatureValid === true &&
session.verificationResult.issuerTrusted === true &&
session.verificationResult.policyCompliant === true &&
session.verificationResult.revocationStatus !== 'revoked';

The checks[] array provides per-check detail (signature, revocation, expiration, issuer_trust, policy, schema) for logging and audit. issuerMemberId identifies which trusted issuer signed the credential — useful for issuer-specific business rules.

Step 5 — Handle a failed session​

failureReasonMeaningRecommended action
expiredSession TTL elapsed before holder presentedCreate a new session
invalid_vpVP token was malformed or signature invalidWallet integration issue — check wallet logs
issuer_not_trustedCredential issuer not in the trust networkContact iDen2 to add the issuer to your trust network
revoked_credentialCredential has been revokedReject the holder; do not grant access
audience_mismatchKB-JWT audience does not match client_idWallet integration issue — check holder key binding
verifier_not_authorizedVerifier configuration issueCheck your token scopes and verifier configuration permissions
policy_violationVerification policy check failedReview your presentation definition policy
internal_errorPlatform errorRetry; contact support if recurring

Diagnosing latency with session timestamps​

Session created: createdAt
Wallet submits VP: vpReceivedAt
Platform verification done: vendorVerifiedAt
Your webhook called: callbackDeliveredAt
Session expires: expiresAt

Large gaps between createdAt and vpReceivedAt are user behaviour (slow to scan); gaps between vpReceivedAt and vendorVerifiedAt are platform verification latency. callbackAttempts > 1 or callbackDeliveryStatus: "exhausted" indicates webhook delivery problems — see Polling vs. Webhooks. Verification can still be completed when webhook delivery fails; always confirm via GET /sessions/{sessionId}.

Production checklist​

  • Credentials in a secrets manager; HTTPS only (Security Best Practices)
  • externalId set on every session
  • Access decisions check the full verificationResult, not just status === "completed"
  • revocationStatus !== "revoked" enforced before granting access
  • Webhook payloads (if used) confirmed via GET /sessions/{sessionId} before granting access
  • Polling stops at expiresAt; expired sessions produce a fresh session