Integrations · Updated 2026-10-20

Block fake signups with Supabase Auth

Screen signups in a Supabase Edge Function before calling auth.signUp, so throwaway and bot accounts never get created.

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.