Skip to main content

QR Code Rendering

Both Credential Factory and Verification Gateway hand you everything you need to put a scannable QR code in front of the holder. You have two options.

Every successful POST /api/v1/iaas/credential-offers and POST /api/v1/verifier/sessions response includes qrCodeDataUrl: a complete, base64-encoded PNG as a data URL.

{
"qrCodeDataUrl": "data:image/png;base64,iVBORw0KGgo..."
}

Render it directly — no QR library required:

<img src="data:image/png;base64,iVBORw0KGgo..." alt="Scan with your wallet" />

In React:

<img src={session.qrCodeDataUrl} alt="Scan with your wallet to share your credential" />

This is the zero-dependency path and is correct for almost every integration.

Option 2 — Render the raw URL with your own QR library​

If you need custom styling (brand colours, embedded logo, specific sizing/error-correction), render the raw offerUrl (Credential Factory) or authorizationUrl (Verification Gateway) with a QR library of your choice.

Node.js with qrcode:

import QRCode from 'qrcode';

const dataUrl = await QRCode.toDataURL(session.authorizationUrl, {
errorCorrectionLevel: 'M',
margin: 2,
width: 320,
});

Browser with qrcode.js:

new QRCode(document.getElementById('qr'), {
text: offer.offerUrl,
width: 320,
height: 320,
});

Treat the URL as opaque — encode it exactly as returned, with no trimming, re-encoding, or parameter manipulation. The authorizationUrl in particular contains URL-encoded JSON (client_metadata) that breaks if double-encoded.

A user on a phone can't scan their own screen. When your flow runs on the same device as the wallet, present the URL as a tappable link — the openid-credential-offer:// and openid4vp:// schemes open the wallet app directly:

{isMobile ? (
<a href={session.authorizationUrl}>Open in your wallet</a>
) : (
<img src={session.qrCodeDataUrl} alt="Scan with your wallet" />
)}

Practical tips​

  • Display size: render at 240–320 px on desktop. The offer/authorization URLs are long, so the QR is dense — too small and cameras struggle.
  • Contrast: dark modules on a light background. Verify scanability if you restyle (dark mode included).
  • Refresh on expiry: the QR is only valid until expiresAt. Hide it and offer a "Generate new code" action once the offer/session expires — a stale QR that scans but fails is worse UX than an explicit refresh.
  • Accessibility: always set meaningful alt text (e.g. "Scan with your wallet to receive your credential"), and offer the deep-link alternative where possible.
  • Don't persist these artifacts: qrCodeDataUrl and the underlying URLs are short-lived secrets — don't log them or store them beyond their TTL (Security Best Practices).