Live demo: demo.proofage.net · Platform: proofage.xyz
This repository is a full-stack reference implementation showing how to integrate ProofAge age verification into a Next.js 15 application. The demo renders Ember Box — a fictional spirits brand — as a realistic age gate use-case: visitors must verify their age before accessing the site's content.
ProofAge is an online age verification and age gate platform. It lets websites and apps verify that users meet a minimum age requirement (e.g. 18+ or 21+) through a hosted, privacy-safe KYC flow — no server-side handling of identity documents required. It covers use-cases such as:
- Alcohol, tobacco, and cannabis e-commerce
- Adult content platforms
- Gambling and gaming sites
- Age-restricted subscriptions and memberships
See the full platform at proofage.xyz and the live demo at demo.proofage.net.
| Layer | What it demonstrates |
|---|---|
| Client SDK | Loading kyc-loader.js dynamically, calling KycService.start({ verificationUrl }), handling onComplete / onError callbacks |
| Server-side session | POST /api/demo-session creates the verification with HMAC signing (@proofage/node), an external_id from an httpOnly visitor cookie and callback_url = /result; the browser opens it with KycService.start({ verificationUrl }) |
| Result page | /result asks ProofAge for the outcome (GET /v1/verifications/{id}, plus the document's first name when approved) and checks external_id against the visitor cookie before showing anything |
| Webhook receiver | POST /api/webhooks/proofage picks the workspace by X-Auth-Client and verifies the signature with its secret |
| Realistic UI | A branded fictional storefront (Ember Box) with members-only content, plus declined, retry and pending screens |
Why create the session on the server: only a signed server request can set
external_id; the browser SDK's unsigned create drops it. The outcome comes from the API, never from the browser'sonComplete, which carries no result.
- Next.js 15 (App Router, Turbopack)
- React 19
- Tailwind CSS v4
@proofage/node— official Node.js SDK for server-side HMAC signing and webhook verification
git clone https://github.com/ProofAge/demo.git
cd demo
npm installcp .env.example .env.localFill in your keys from your ProofAge workspace:
# Public URL of this demo (metadata, sitemap, and the /result redirect). No trailing slash.
NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Server-only: one entry per demo page, keyed by slug. The public key reaches the
# browser SDK through the page; the secret key signs API calls and verifies webhooks.
# ember-box → /
# eudi-wallet → /eudi-wallet-age-verification
PROOFAGE_DEMO_WORKSPACES={"ember-box":{"apiKey":"pk_test_...","secretKey":"sk_test_..."},"eudi-wallet":{"apiKey":"pk_test_...","secretKey":"sk_test_..."}}
# Defaults baked into the code — only override for local dev or custom deployments:
# NEXT_PUBLIC_PROOFAGE_API_URL=https://api.proofage.net/v1
# NEXT_PUBLIC_PROOFAGE_SDK_URL=https://app.proofage.net/sdk-build/kyc-loader.js
# PROOFAGE_BASE_URL=https://api.proofage.netEach demo page has its own workspace; add an entry to PROOFAGE_DEMO_WORKSPACES and a page to add a demo.
Use test keys during development. Get them at proofage.xyz.
npm run devOpen http://localhost:3000.
ProofAge needs to reach your webhook endpoint to deliver age verification results. Use a tunnel during local development:
# ngrok
ngrok http 3000
# or Cloudflare Tunnel
cloudflared tunnel --url http://localhost:3000Set NEXT_PUBLIC_SITE_URL to the public HTTPS URL the tunnel provides. The demo-session route uses it to build the callback_url (/result) sent to ProofAge.
vercelOr connect the repository in the Vercel dashboard. Set the same environment variables in Project Settings → Environment Variables, then:
- Set
NEXT_PUBLIC_SITE_URLto your production URL (e.g.https://demo.proofage.net) - In your ProofAge dashboard, configure the webhook URL to
https://your-domain/api/webhooks/proofage
app/
page.tsx # Age gate page (Ember Box storefront)
result/page.tsx # Result page (outcome from the ProofAge API)
layout.tsx # Root layout with fonts and metadata
api/
demo-session/route.ts # Create (POST), read (GET), clear (DELETE) the demo session
webhooks/proofage/route.ts # Webhook receiver, verified with the sending workspace's keys
components/
Hero.tsx # Main age gate UI
VerifyButton.tsx # Creates the session, then starts the ProofAge SDK flow
ResultScreen.tsx # Result states, polling while a decision is pending
MembersContent.tsx # Members-only content shown when approved
Footer.tsx
lib/
proofage.ts # Per-workspace ProofAge clients
demo-workspaces.ts # PROOFAGE_DEMO_WORKSPACES parsing
demo-cookies.ts # Visitor and session cookies
demo-result.ts # Pure mapping from API status to what the page shows
- ProofAge platform — sign up, manage workspaces, view verification logs
- Live demo — see this code running in production
- ProofAge Node.js SDK —
@proofage/nodeon npm
| Platform | Repository | Use-case |
|---|---|---|
| WordPress | ProofAge/wordpress-plugin | Age gate plugin for WordPress — WooCommerce age verification, age-restricted pages, adult content gating |
| Laravel | ProofAge/laravel-client | Laravel age verification client — HMAC-signed API calls, webhook handling, middleware for age-restricted routes |
| Next.js | this repo | Full-stack age verification demo with JS SDK, server routes, and webhook receiver |