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
}
credentialTypeandpresentationDefinitionIdselect what you're asking for; omit them to use your configuration's defaults.ttlSecondsdefaults 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) returns400(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:
| Parameter | Location | Purpose |
|---|---|---|
client_id | Query param in authorizationUrl | Internal identifier used for KB-JWT audience validation. Not for display or trust lookup. |
verifier_member_id | Inside 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:
| Status | Terminal? | Meaning |
|---|---|---|
pending | No | Waiting for the holder |
completed | Yes | Presentation received and verification passed |
failed | Yes | Check 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 oncredentialData, 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
failureReason | Meaning | Recommended action |
|---|---|---|
expired | Session TTL elapsed before holder presented | Create a new session |
invalid_vp | VP token was malformed or signature invalid | Wallet integration issue — check wallet logs |
issuer_not_trusted | Credential issuer not in the trust network | Contact iDen2 to add the issuer to your trust network |
revoked_credential | Credential has been revoked | Reject the holder; do not grant access |
audience_mismatch | KB-JWT audience does not match client_id | Wallet integration issue — check holder key binding |
verifier_not_authorized | Verifier configuration issue | Check your token scopes and verifier configuration permissions |
policy_violation | Verification policy check failed | Review your presentation definition policy |
internal_error | Platform error | Retry; 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)
-
externalIdset on every session - Access decisions check the full
verificationResult, not juststatus === "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