Run the check on the server, in the same server action or route handler that creates the account. Your API key never reaches the browser.
1. Add your key
# .env.local
VEQEN_API_KEY=vq_live_...
2. A small helper
// lib/veqen.ts
import "server-only";
export type Verdict = "admit" | "review" | "refuse";
export async function screenSignup(input: { email: string; ip?: string | null; phone?: string }) {
try {
const res = await fetch("https://veqen-app.com/v1/signup/check", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VEQEN_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(input),
cache: "no-store",
signal: AbortSignal.timeout(4000),
});
if (!res.ok) return { verdict: "review" as Verdict, result: null };
const result = await res.json();
return { verdict: result.risk.verdict as Verdict, result };
} catch {
return { verdict: "review" as Verdict, result: null }; // fail open
}
}
3. Use it in a server action
// app/signup/actions.ts
"use server";
import { headers } from "next/headers";
import { screenSignup } from "@/lib/veqen";
export async function signUp(formData: FormData) {
const email = String(formData.get("email") ?? "");
const ip = (await headers()).get("x-forwarded-for")?.split(",")[0]?.trim() ?? null;
const { verdict, result } = await screenSignup({ email, ip });
if (result?.email?.did_you_mean) {
return { error: `Did you mean ${result.email.did_you_mean}?` };
}
if (verdict === "refuse") {
return { error: "Please use a permanent email address." };
}
const user = await createUser({
email,
canonicalEmail: result?.email?.canonical ?? email.toLowerCase(),
requiresVerification: verdict === "review",
});
return { ok: true, userId: user.id };
}
On Vercel, x-forwarded-for is set by the platform, so its first address is the visitor. Behind your own proxy, make sure the proxy sets it.
Route handler version
// app/api/signup/route.ts
import { NextResponse } from "next/server";
import { screenSignup } from "@/lib/veqen";
export async function POST(request: Request) {
const { email, password } = await request.json();
const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? null;
const { verdict } = await screenSignup({ email, ip });
if (verdict === "refuse") {
return NextResponse.json({ error: "Please use a permanent email address." }, { status: 400 });
}
// ...create the user
return NextResponse.json({ ok: true });
}
Using an auth library?
If NextAuth/Auth.js, Clerk, Supabase or Firebase creates the user for you, see their pages: Clerk, Supabase, Firebase.