Route signups through an Edge Function: it checks the details, then creates the user with the admin API. Your browser code calls the function instead of supabase.auth.signUp directly.
1. Store the key
supabase secrets set VEQEN_API_KEY=vq_live_...
2. The Edge Function
// supabase/functions/signup/index.ts
import { createClient } from "jsr:@supabase/supabase-js@2";
const admin = createClient(Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!);
Deno.serve(async (req) => {
const { email, password } = await req.json();
const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
let result: any = null;
try {
const res = await fetch("https://veqen-app.com/v1/signup/check", {
method: "POST",
headers: { Authorization: `Bearer ${Deno.env.get("VEQEN_API_KEY")}`, "Content-Type": "application/json" },
body: JSON.stringify({ email, ip }),
signal: AbortSignal.timeout(4000),
});
if (res.ok) result = await res.json();
} catch {
// fail open
}
if (result?.risk.verdict === "refuse") {
return Response.json({ error: "Please use a permanent email address." }, { status: 400 });
}
const { data, error } = await admin.auth.admin.createUser({
email,
password,
email_confirm: false,
app_metadata: { veqen_verdict: result?.risk.verdict ?? "review" },
});
if (error) return Response.json({ error: error.message }, { status: 400 });
return Response.json({ id: data.user.id });
});
3. Call it from your app
const { data, error } = await supabase.functions.invoke("signup", { body: { email, password } });
Then send the confirmation email (or a magic link) as usual. To stop people bypassing the function, turn off public email signups in Authentication → Providers → Email so only your function can create users.