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 Integration

Complete working example of age verification in your app using direct HTTP integration.


The Flow

1. User clicks "Verify age"
2. Your app calls POST /v1/verify/age
3. PYLON returns walletUrl
4. User scans QR code with EUDI wallet
5. Wallet asks: "Share age > 18?"
6. User taps Accept/Deny
7. Wallet sends proof to PYLON
8. PYLON validates and fires webhook
9. Your app gets result and grants/denies access

Node.js + Express

import express from 'express';
import crypto from 'crypto';
import fetch from 'node-fetch';
import QRCode from 'qrcode';

const app = express();
app.use(express.json());

// 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();
  
  // Generate QR code
  const qr = await QRCode.toDataURL(data.walletUrl);

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

// 2. Handle webhook
app.post('/api/webhooks/pylon', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['x-pylon-signature'];
  const body = req.body.toString();
  
  // Validate signature
  function validateSignature(sig, body, secret) {
    const [t, v1] = sig.split(',');
    const tValue = t.replace('t=', '');
    const signedMessage = `${tValue}.${body}`;
    const computed = crypto.createHmac('sha256', secret).update(signedMessage).digest('hex');
    return crypto.timingSafeEqual(Buffer.from(v1.replace('v1=', '')), Buffer.from(computed));
  }
  
  if (!validateSignature(signature, body, process.env.PYLON_WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const payload = JSON.parse(body);
  const { verificationId, result, attributes } = payload;

  if (result === 'verified' && attributes.ageOver18) {
    console.log(`✅ User verified as age > 18`);
  } else {
    console.log(`❌ Verification failed or rejected`);
  }

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

app.listen(3000);

Python + Flask

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

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.QRCode(version=1, box_size=10, border=5)
    qr.add_data(data['walletUrl'])
    qr.make(fit=True)

    img = qr.make_image(fill_color="black", back_color="white")
    buf = BytesIO()
    img.save(buf)
    qr_base64 = base64.b64encode(buf.getvalue()).decode()

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

def validate_signature(signature, body, secret):
    parts = signature.split(',')
    t = parts.replace('t=', '')
    v1 = parts.replace('v1=', '')
    
    signed_message = f"{t}.{body}"
    computed = hmac.new(secret.encode(), signed_message.encode(), hashlib.sha256).hexdigest()
    
    return hmac.compare_digest(v1, computed)

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

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

    data = request.json

    if data['result'] == 'verified' and data.get('attributes', {}).get('ageOver18'):
        print(f"✅ User {data['verificationId']} verified as age > 18")
        return {'received': True}, 200

    return {'received': True}, 200

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

Testing Locally

# Start local emulator
pylon-cli

# In another terminal, start your app
node app.js  # (or python app.py)

# Make request
curl -X POST http://localhost:3000/api/verify-age

# Emulator auto-completes immediately

Error Handling

try {
  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.status === 401) {
    console.error('Invalid API key');
  } else if (response.status === 429) {
    console.error('Rate limited');
  } else if (response.status === 400) {
    console.error('Invalid callback URL (must be HTTPS)');
  }
} catch (error) {
  console.error('Network error:', error);
}

Next: API Reference | Troubleshooting