Back
DOCS
OverviewQuick StartEmbed WidgetEventsAPI ReferenceStatus PollingDocumentsRetentionBYOSExamples
OverviewQuick StartEmbed WidgetEventsAPI ReferenceStatus PollingDocumentsRetentionBYOSExamples
OverviewQuick StartEmbed WidgetEventsAPI ReferenceStatus PollingDocumentsRetentionBYOSExamples
  1. Home
  2. Docs
  3. Examples
  4. Next.js

Next.js Example

A Next.js App Router integration with a client component for the embed widget and a server-side API route that proxies status checks with the secret key.

Client Component

Create a 'use client' component that renders the iframe and handles postMessage events. This component uses the publishable key which is safe for browser use.

'use client';

import { useState, useEffect, useCallback } from 'react';

const AGEEVIDENCE_URL = 'https://ageevidence.com';

interface VerificationEmbedProps {
  apiKey: string;
  externalId: string;
  level?: 'age_only' | 'full_age' | 'full_kyc';
  theme?: 'light' | 'dark' | 'auto';
  locale?: 'en' | 'es' | 'pt' | 'fr';
}

export function VerificationEmbed({
  apiKey,
  externalId,
  level = 'full_age',
  theme = 'dark',
  locale = 'en',
}: VerificationEmbedProps) {
  const [loaded, setLoaded] = useState(false);
  const [result, setResult] = useState<{
    status: string;
    verificationId?: string;
  } | undefined>(undefined);

  const handleMessage = useCallback((event: MessageEvent) => {
    if (event.origin !== AGEEVIDENCE_URL) return;

    const { type, data } = event.data;

    switch (type) {
      case 'ae:ready':
        setLoaded(true);
        break;
      case 'ae:complete':
        setResult({ status: 'submitted', verificationId: data.verificationId });
        pollStatus(externalId);
        break;
      case 'ae:error':
        setResult({ status: `error: ${data.error}` });
        break;
      case 'ae:cancel':
        setResult({ status: 'cancelled' });
        break;
    }
  }, [externalId]);

  useEffect(() => {
    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, [handleMessage]);

  const pollStatus = async (id: string) => {
    // Call YOUR API route (see api/verify/status/route.ts below)
    const res = await fetch(`/api/verify/status?externalId=${id}`);
    const data = await res.json();

    if (data.data?.status === 'approved') {
      setResult({ status: 'approved' });
    } else if (data.data?.status === 'rejected') {
      setResult({ status: 'rejected' });
    } else {
      // Continue polling (see Status Polling docs for backoff)
      setTimeout(() => pollStatus(id), 5000);
    }
  };

  const params = new URLSearchParams({
    apiKey,
    externalId,
    theme,
    locale,
    parentOrigin: typeof window !== 'undefined' ? window.location.origin : '',
  });

  if (result?.status === 'approved') {
    return <div>Verification approved.</div>;
  }

  return (
    <div style={{ position: 'relative' }}>
      {!loaded && <div className="absolute inset-0 flex items-center justify-center bg-gray-900 rounded-xl">Loading...</div>}
      <iframe
        src={`${AGEEVIDENCE_URL}/embed/${level}?${params}`}
        width="100%"
        height="600px"
        frameBorder="0"
        allow="camera"
        style={{ border: 'none', borderRadius: 12, opacity: loaded ? 1 : 0 }}
      />
    </div>
  );
}

API Route (Server-Side)

Create an API route that proxies status checks to AgeEvidence using the secret key. This key is only available on the server through environment variables.

// app/api/verify/status/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const externalId = request.nextUrl.searchParams.get('externalId');

  if (!externalId) {
    return NextResponse.json({ error: 'Missing externalId' }, { status: 400 });
  }

  const response = await fetch(
    `https://ageevidence.com/v1/verify/${externalId}/status`,
    {
      headers: {
        'X-API-Key': process.env.AGEEVIDENCE_SECRET_KEY!, // sk_verify_...
      },
    }
  );

  if (!response.ok) {
    return NextResponse.json(
      { error: 'Failed to fetch status' },
      { status: response.status }
    );
  }

  const data = await response.json();
  return NextResponse.json(data);
}

Add your secret key to .env.local:

AGEEVIDENCE_SECRET_KEY=sk_verify_YOUR_SECRET_KEY

Page Usage

Use the client component in a page. The page itself can be a server component.

// app/verify/page.tsx
import { VerificationEmbed } from '@/components/VerificationEmbed';

export default function VerifyPage() {
  return (
    <main className="max-w-2xl mx-auto py-12 px-4">
      <h1 className="text-2xl font-bold mb-6">Verify Your Identity</h1>
      <VerificationEmbed
        apiKey="pk_verify_YOUR_KEY"
        externalId="user_123"
        level="full_age"
        theme="dark"
        locale="en"
      />
    </main>
  );
}

Notes

  • The client component must be marked with 'use client' because it uses useState and useEffect.
  • The API route runs on the server and safely holds the secret key.
  • For production, add proper error handling and polling backoff. See the Status Polling guide.
  • The allow="camera" attribute on the iframe is required.
© 2026 AgeEvidence. All rights reserved.
DocsPricingPrivacyTermsContact