Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Example: Age Verification

Complete working examples of age verification integration.


The Flow

1. User clicks "Verify age" in your app
2. Your backend calls POST /v1/verify/age
3. PylonID returns walletUrl
4. Your frontend displays walletUrl as QR code
5. User scans QR with EUDI wallet
6. Wallet shows: "Share age_over_18?"
7. User consents → wallet sends proof to PylonID
8. PylonID validates → fires webhook to your backend
9. Your app grants or denies access

Node.js + Express

import express from 'express';
import crypto from 'crypto';
import QRCode from 'qrcode';

const app = express();

// 1. Start verification
app.post('/api/verify-age', async (req, res) => {
  const response = await fetch('https://pylonid.eu/v1/verify/age', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.PYLON_API_KEY}`
    },
    body: JSON.stringify({
      policy: { minAge: 18 },
      callbackUrl: 'https://yourapp.com/api/webhooks/pylon'
    })
  });

  const data = await response.json();
  const qr = await QRCode.toDataURL(data.walletUrl);

  res.json({
    verificationId: data.verificationId,
    qrCode: qr,
    walletUrl: data.walletUrl
  });
});

// 2. Handle webhook
function validateSignature(body, signature, secret) {
  const computed = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computed));
}

app.post('/api/webhooks/pylon', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-pylon-signature'];

  if (!validateSignature(req.body, signature, process.env.PYLON_WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { verificationId, status, result } = JSON.parse(req.body);

  if (status === 'verified' && result.age_over_18) {
    console.log(`✅ ${verificationId}: age verified`);
    // Grant access in your database
  } else {
    console.log(`❌ ${verificationId}: ${status}`);
  }

  res.status(200).json({ received: true });
});

app.listen(3000);

Python + Flask

import os, hmac, hashlib, requests, qrcode, base64
from io import BytesIO
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/verify-age', methods=['POST'])
def start_verification():
    response = requests.post(
        'https://pylonid.eu/v1/verify/age',
        json={
            'policy': {'minAge': 18},
            'callbackUrl': 'https://yourapp.com/webhook/pylon'
        },
        headers={'Authorization': f"Bearer {os.getenv('PYLON_API_KEY')}"}
    )
    data = response.json()

    # Generate QR code
    qr = qrcode.make(data['walletUrl'])
    buf = BytesIO()
    qr.save(buf)
    qr_b64 = base64.b64encode(buf.getvalue()).decode()

    return jsonify({
        'verificationId': data['verificationId'],
        'qrCode': f'data:image/png;base64,{qr_b64}',
        'walletUrl': data['walletUrl']
    })

def validate_signature(body, signature, secret):
    computed = 'sha256=' + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, computed)

@app.route('/webhook/pylon', methods=['POST'])
def pylon_webhook():
    signature = request.headers.get('X-Pylon-Signature', '')
    body = request.get_data()
    secret = os.getenv('PYLON_WEBHOOK_SECRET')

    if not validate_signature(body, signature, secret):
        return {'error': 'Invalid signature'}, 401

    data = request.json

    if data['status'] == 'verified' and data.get('result', {}).get('age_over_18'):
        print(f"✅ {data['verificationId']}: age verified")
    else:
        print(f"❌ {data['verificationId']}: {data['status']}")

    return {'received': True}, 200

if __name__ == '__main__':
    app.run(port=5000)

Error Handling

const response = await fetch('https://pylonid.eu/v1/verify/age', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${process.env.PYLON_API_KEY}`
  },
  body: JSON.stringify({
    policy: { minAge: 18 },
    callbackUrl: 'https://yourapp.com/webhook'
  })
});

if (!response.ok) {
  switch (response.status) {
    case 401: console.error('Invalid API key'); break;
    case 400: console.error('Invalid request (callbackUrl must be HTTPS)'); break;
    case 429: console.error('Rate limited — retry later'); break;
    default:  console.error(`Error: ${response.status}`); break;
  }
  return;
}

const data = await response.json();
// Display data.walletUrl as QR code

Polling as Backup

If webhooks aren’t suitable, poll the verification status:

async function waitForResult(verificationId) {
  for (let i = 0; i < 60; i++) {
    const resp = await fetch(`https://pylonid.eu/v1/status/${verificationId}`, {
      headers: { 'Authorization': `Bearer ${process.env.PYLON_API_KEY}` }
    });
    const data = await resp.json();

    if (data.status !== 'pending') return data;
    await new Promise(r => setTimeout(r, 2000)); // Poll every 2s
  }
  return { status: 'timeout' };
}

Next: API Reference | Webhooks | Troubleshooting