Technical

OpenEUDI SDK Quickstart: Your First Verification in 5 Minutes

A step-by-step tutorial to install the OpenEUDI SDK, create a verification session, and handle the result — all in under 5 minutes using DEMO mode.

eIDAS Pro Team
March 24, 2026
6 min read

Corrected 20 October 2026: an earlier version of this post listed age_over_18-style attributes and said the wallet proves the boolean without revealing the birth date. The EUDI PID has no age attribute: the verifier requests the birth date and derives the yes/no itself. The code samples now also match the published @openeudi/core API.

Prerequisites

  • Node.js 20+ installed
  • TypeScript project (or JavaScript with ESM)
  • A terminal and your favorite editor

That's it. No API keys, no registration, no WRPAC certificate. DEMO mode works out of the box.

Step 1: Install

npm install @openeudi/core

Or with your preferred package manager:

pnpm add @openeudi/core
yarn add @openeudi/core

Step 2: Create a Verifier

import { VerificationService, DemoMode, InMemorySessionStore } from '@openeudi/core';

// Initialize in DEMO mode — no credentials needed
const service = new VerificationService({
  mode: new DemoMode(),
  store: new InMemorySessionStore(), // optional: this is the default store
});

The mode option controls how the SDK behaves:

ModeBehaviorUse Case
new DemoMode()Auto-completes after 3 seconds (delayMs to change it)Showcases, sales demos, landing pages
new MockMode()Simulates wallet responses with configurable dataIntegration testing, CI/CD pipelines
Your own IVerificationModeReal EUDI Wallet verification (OpenID4VP via @openeudi/openid4vp)Live verifications (requires a WRPAC)

Step 3: Create a Verification Session

import { VerificationType } from '@openeudi/core';

const session = await service.createSession({ type: VerificationType.AGE });

// session.id is the unique session identifier
// session.walletUrl is the OpenID4VP link: encode it as a QR code, or open it on mobile
// session.expiresAt is when the session times out (default: 5 minutes)

The type field specifies what you want to verify. eIDAS Pro is designed around privacy-first boolean results — your code gets a yes/no answer, never raw personal data:

TypeResult fieldsDescription
VerificationType.AGEageVerifiedIs the user 18 or older?
VerificationType.COUNTRYcountry, countryVerifiedIs the user's country allowed?
VerificationType.BOTHAll of the aboveAge and country in one session

Country compliance (which countries are allowed) is set per session with countryWhitelist or countryBlacklist (not both) — the SDK compares the credential's country against that list and returns countryVerified.

Note: In DEMO mode, results are synthetic. With a real wallet, the age answer is derived: the EUDI PID has no age_over_18 attribute, so the verifier requests the birth date alone (selective disclosure), computes the 18+ threshold and keeps only ageVerified. A yes/no without any birth date needs the separate proof-of-age attestation (eu.europa.ec.av.1), which only Denmark issues in production so far.

Step 4: Display the QR Code

The session's walletUrl becomes a QR code that the user scans with their EUDI Wallet app.

// Requires the optional peer dependency: npm install qrcode
import { generateQRCode } from '@openeudi/core/qr';

const qrCodeUrl = await generateQRCode(session.walletUrl); // base64 PNG data URL

// In a web context
const img = document.createElement('img');
img.src = qrCodeUrl;
document.getElementById('qr-container').appendChild(img);

// In React
function VerificationQR({ qrCodeUrl }) {
  return <img src={qrCodeUrl} alt="Scan with your EUDI Wallet" />;
}

// In Next.js (using next/image)
import Image from 'next/image';
function VerificationQR({ qrCodeUrl }) {
  return <Image src={qrCodeUrl} alt="Scan with your EUDI Wallet" width={256} height={256} />;
}

Step 5: Listen for the Result

// Event-based (recommended for web apps)
service.on('session:verified', (session, result) => {
  console.log('Verification successful!');
  console.log('Verified:', result.verified);        // true
  console.log('Age verified:', result.ageVerified); // true
  console.log('Session ID:', session.id);
  console.log('Timestamp:', session.completedAt);
});

service.on('session:rejected', (session, reason) => {
  console.log('Verification rejected:', reason);
});

service.on('session:expired', (session) => {
  console.log('Session expired — generate a new QR code');
});

// Expiry is detected by a sweep, so run it periodically
setInterval(() => service.cleanupExpired(), 60_000);

Or read the session state on demand:

import { VerificationStatus } from '@openeudi/core';

const current = await service.getSession(session.id);
if (current.status === VerificationStatus.VERIFIED) {
  console.log('Age verified:', current.result.ageVerified);
}

Complete Example: Express.js API Route

import express from 'express';
import { VerificationService, DemoMode, InMemorySessionStore, VerificationType } from '@openeudi/core';
import type { CompletedSession, VerificationResult } from '@openeudi/core';
import { generateQRCode } from '@openeudi/core/qr';

const app = express();
const service = new VerificationService({
  mode: new DemoMode(),
  store: new InMemorySessionStore(),
});

// Create a new verification session
app.post('/api/verify', async (req, res) => {
  const session = await service.createSession({ type: VerificationType.AGE });

  res.json({
    sessionId: session.id,
    qrCodeUrl: await generateQRCode(session.walletUrl),
  });
});

// Check session status (or use SSE for real-time)
app.get('/api/verify/:sessionId', async (req, res) => {
  const session = await service.getSession(req.params.sessionId);
  res.json({ status: session.status, result: 'result' in session ? session.result : null });
});

// SSE endpoint for real-time updates
app.get('/api/verify/:sessionId/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  const { sessionId } = req.params;

  // Events fire for every session, so filter on the one this client watches
  const onVerified = (session: CompletedSession, result: VerificationResult) => {
    if (session.id !== sessionId) return;
    res.write(`data: ${JSON.stringify({ type: 'verified', result })}\n\n`);
    res.end();
  };
  const onRejected = (session: CompletedSession, reason: string) => {
    if (session.id !== sessionId) return;
    res.write(`data: ${JSON.stringify({ type: 'rejected', reason })}\n\n`);
    res.end();
  };

  service.on('session:verified', onVerified);
  service.on('session:rejected', onRejected);
  req.on('close', () => {
    service.off('session:verified', onVerified);
    service.off('session:rejected', onRejected);
  });
});

app.listen(3000, () => console.log('Server running on port 3000'));

What Happens in DEMO Mode?

When you run this code in DEMO mode:

  1. A QR code is generated (visually identical to a real one)
  2. After 3 seconds, the session automatically completes
  3. A synthetic result is returned (verified: true, ageVerified: true)
  4. Your session:verified listener fires with the result

No real wallet is involved. This lets you build and test the entire verification UI and backend flow before EUDI Wallets launch.

Next Steps

  • Switch to MockMode to test with configurable responses (a default result, or per-session overrides with setSessionResult())
  • Add the WooCommerce plugin to your WordPress store for checkout verification
  • Read the API reference for advanced configuration (session TTL, custom session stores, error handling)
  • Join the GitHub discussions to report issues or request features

When EUDI Wallets launch, swap new DemoMode() for a production IVerificationMode built on @openeudi/openid4vp and your WRPAC credentials — or use eIDAS Pro's managed service to skip the certificate management entirely.


OpenEUDI is Apache-2.0-licensed and published on npm — star the GitHub repo to get notified of new releases.

Related Articles

Share this article

Help others learn about eIDAS verification