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

Local Testing with Emulator

Test the full age verification flow offline, without a real EUDI wallet.


Quick Start

cd pylon_cli
cargo build --release
./target/release/pylon-cli

The emulator starts on http://localhost:7777 and provides the same API as the production server.


Test Workflow

Step 1: Create a Verification

curl -X POST http://localhost:7777/v1/verify/age \
  -H "Content-Type: application/json" \
  -d '{
    "policy": { "minAge": 18 },
    "callbackUrl": "http://localhost:3000/webhook"
  }'

Response:

{
  "verificationId": "ver_local_ABC123",
  "status": "pending",
  "walletUrl": "http://localhost:7777/scan/ver_local_ABC123"
}

Step 2: Simulate Wallet Response

Open http://localhost:7777/scan/ver_local_ABC123 in your browser. You’ll see a simple UI with Accept and Reject buttons.

  • Accept → emulator fires webhook with "status": "verified"
  • Reject → emulator fires webhook with "status": "rejected"

Step 3: Receive Webhook

Your webhook endpoint at http://localhost:3000/webhook receives:

{
  "event": "verification.completed",
  "verificationId": "ver_local_ABC123",
  "status": "verified",
  "result": { "age_over_18": true },
  "timestamp": "2026-04-22T00:05:00Z"
}

Integration Example (Express.js)

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

// Start verification
app.get('/start', async (req, res) => {
  const resp = await fetch('http://localhost:7777/v1/verify/age', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      policy: { minAge: 18 },
      callbackUrl: 'http://localhost:3000/webhook'
    })
  });
  const data = await resp.json();
  res.json(data); // Return walletUrl to display as QR
});

// Receive webhook
app.post('/webhook', (req, res) => {
  console.log(`${req.body.verificationId}: ${req.body.status}`);
  res.status(200).json({ received: true });
});

app.listen(3000, () => console.log('App on :3000'));

Test it:

# Terminal 1: Start your app
node app.js

# Terminal 2: Start emulator
cd pylon_cli && cargo run --release

# Terminal 3: Trigger verification
curl http://localhost:3000/start
# Open the returned walletUrl in browser, click Accept

Emulator vs Production

AspectEmulatorProduction
URLhttp://localhost:7777https://pylonid.eu
WalletBrowser UI (Accept/Reject)Real EUDI wallet app
SignaturesNo SD-JWT-VC validationFull ES256 + JWKS verification
WebhooksFires immediately, no retriesRetries with exponential backoff
StorageIn-memory (clears on restart)PostgreSQL
AuthNo API key requiredAPI key required
Webhook signingNo signatureHMAC-SHA256 signed

Prerequisites

# Rust 1.75+
rustc --version

# Build
cd pylon_cli
cargo build --release

Troubleshooting

Port 7777 already in use

lsof -i :7777
kill -9 <PID>

Webhook not firing

Test that your endpoint is reachable:

curl -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -d '{"test": true}'

Connection refused

Emulator not running. Start it with cargo run --release from the pylon_cli directory.


Next Steps

Once your integration works locally:

  1. Switch to https://pylonid.eu with a real API key
  2. Add webhook signature validation (emulator doesn’t sign, production does)
  3. Test with a real EUDI wallet

See Integration & Testing for the full testing progression.


Questions? See Troubleshooting or email hello@pylonid.eu