Learn to build privacy-preserving identity verification with Self Protocol - from frontend QR codes to backend API verification.
πΊ New to Self? Watch the ETHGlobal Workshop first.
This main branch of the repo contains an example of onchain verification. If you would like to see an example with offchain/backend verification, please check out the 'backend-verification' branch.
main: on chain verificationbackend-verification: off chain/backend verificationhyperlane-example: onchain verification w/ Hyperlane bridging
- Node.js 20+
- Self Mobile App
- ngrok (for local development)
# Clone the workshop repository
git clone <repository-url>
git switch backend-verification
cd workshop/app
# Install dependencies
npm installFor local development, you need a publicly accessible endpoint. Start ngrok in a separate terminal:
# Install ngrok if you haven't already
# macOS: brew install ngrok
# Or download from: https://ngrok.com/download
# Start ngrok tunnel to port 3000
ngrok http 3000Keep ngrok running and note the URL (e.g., https://abc123.ngrok-free.app).
π‘ Tip: ngrok creates a tunnel so the Self app relayers can reach your local backend API endpoint at
/api/verify.
Configure the application:
# Create copy of env
cp .env.example .envEdit .env with your ngrok URL from Step 2:
# Your ngrok URL from Step 2 + /api/verify
NEXT_PUBLIC_SELF_ENDPOINT=https://your-ngrok-url.ngrok-free.app/api/verify
# App configuration
NEXT_PUBLIC_SELF_APP_NAME="Self Workshop"
NEXT_PUBLIC_SELF_SCOPE_SEED="self-workshop"
β οΈ Important: The endpoint must be publicly accessible. Update it each time you restart ngrok.
# Start the Next.js development server
cd app
npm run devVisit http://localhost:3000 to see your verification application!
Test the flow:
- Open the app at
http://localhost:3000 - Scan the QR code with the Self mobile app
- Complete verification on your phone
- The backend API will verify the proof and return results
- You'll be redirected to the success page
The Self SDK is configured in your React components (app/app/page.tsx):
import { SelfAppBuilder, countries } from '@selfxyz/qrcode';
const app = new SelfAppBuilder({
version: 2, // Always use V2
appName: process.env.NEXT_PUBLIC_SELF_APP_NAME,
scope: process.env.NEXT_PUBLIC_SELF_SCOPE_SEED,
endpoint: process.env.NEXT_PUBLIC_SELF_ENDPOINT, // Your ngrok URL + /api/verify
logoBase64: "https://i.postimg.cc/mrmVf9hm/self.png", // Logo URL or base64
userId: userId, // User's identifier (Ethereum address)
endpointType: "staging_https", // "staging_https" for testnet backend, "https" for mainnet
userIdType: "hex", // "hex" for Ethereum addresses
userDefinedData: "Hola Buenos Aires!!!", // Optional custom data
disclosures: {
// Verification requirements (must match your backend config)
minimumAge: 18,
excludedCountries: [countries.UNITED_STATES], // Use country constants
// ofac: true, // Optional: OFAC compliance checking
// Optional disclosures (uncomment to request):
// name: true,
// issuing_state: true,
// nationality: true,
// date_of_birth: true,
// passport_number: true,
// gender: true,
// expiry_date: true,
}
}).build();Your backend verification endpoint is at app/api/verify/route.ts:
import { NextResponse } from "next/server";
import { SelfBackendVerifier, AllIds, DefaultConfigStore } from "@selfxyz/core";
// Initialize the verifier (runs once when server starts)
const selfBackendVerifier = new SelfBackendVerifier(
process.env.NEXT_PUBLIC_SELF_SCOPE_SEED || "self-workshop",
process.env.NEXT_PUBLIC_SELF_ENDPOINT || "http://localhost:3000/api/verify",
true, // mockPassport: true = staging/testnet, false = mainnet
AllIds,
new DefaultConfigStore({
minimumAge: 18,
excludedCountries: ["USA"],
ofac: false,
}),
"hex" // userIdentifierType must match frontend userIdType
);
export async function POST(req: Request) {
try {
const { attestationId, proof, publicSignals, userContextData } = await req.json();
// Verify all required fields are present
if (!proof || !publicSignals || !attestationId || !userContextData) {
return NextResponse.json({
message: "Proof, publicSignals, attestationId and userContextData are required",
}, { status: 200 });
}
// Verify the proof
const result = await selfBackendVerifier.verify(
attestationId,
proof,
publicSignals,
userContextData
);
// Check if verification was successful
if (result.isValidDetails.isValid) {
return NextResponse.json({
status: "success",
result: true,
credentialSubject: result.discloseOutput,
});
} else {
return NextResponse.json({
status: "error",
result: false,
reason: "Verification failed",
details: result.isValidDetails,
}, { status: 200 });
}
} catch (error) {
return NextResponse.json({
status: "error",
result: false,
reason: error instanceof Error ? error.message : "Unknown error",
}, { status: 200 });
}
}Important: Frontend disclosures must match backend DefaultConfigStore configuration.
- Use for: Development and testing
- Supports: Mock passports from Self app
- Hub Address:
0x16ECBA51e18a4a7e61fdC417f0d47AFEeDfbed74 - Network: Celo Sepolia testnet
- RPC:
https://forno.celo-sepolia.celo-testnet.org
- Use for: Production deployments
- Supports: Real passport verification only
- Hub Address:
0xe57F4773bd9c9d8b6Cd70431117d353298B9f5BF - Network: Celo Mainnet
- RPC:
https://forno.celo.org
Note: The backend verifier connects to Celo blockchain to verify merkle roots and registry contracts, but verification logic runs on your server.
- π± Telegram Community: Self Protocol Builders Group
- π Documentation: docs.self.xyz
- π₯ Workshop Video: ETHGlobal Cannes
self-integration-example/
βββ app/ # Next.js application
βββ app/
β βββ api/
β β βββ verify/
β β βββ route.ts # Backend verification API endpoint
β βββ page.tsx # Main QR code page with Self SDK integration
β βββ layout.tsx # Root layout with metadata
β βββ globals.css # Global styles
β βββ verified/
β βββ page.tsx # Success page after verification
β βββ page.module.css # Success page styles
βββ .env.example # Environment template
βββ package.json # Dependencies
βββ tailwind.config.ts # Tailwind CSS configuration
βββ README.md # Documentation
- Self Protocol Docs - Complete protocol documentation
- Backend Integration Guide - Backend verification specifics
- SelfBackendVerifier API - Backend API reference
- Frontend SDK Reference - Frontend integration details
- Disclosure Proofs - Available verification options
- Self on iOS - iOS App
- Self on Android - Android App