What you will build
An Edge Function that Supabase Auth calls for each new signup. It checks the webhook signature, verifies the address with VeriDock, and either allows the signup or returns an error message your signup form can show.
Your API key stays in Supabase secrets, so nothing reaches the browser and you do not need to change your existing signup code.
1. Write the Edge Function
The function fails open: if VeriDock is slow or unreachable, the signup goes ahead. An outage on our side should never stop people joining your product. Only clear bad results are rejected, and risky results such as role accounts are allowed through.
import { Webhook } from "https://esm.sh/standardwebhooks@1.0.0";
const hookSecret = Deno.env.get("BEFORE_USER_CREATED_HOOK_SECRET")!.replace("v1,whsec_", "");
const apiKey = Deno.env.get("VERIDOCK_API_KEY")!;
const json = (body: unknown, status = 200) =>
new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
const reject = (message: string) => json({ error: { http_code: 400, message } }, 400);
async function verify(email: string) {
const url = new URL("https://app.veridock.io/v1/verify");
url.searchParams.set("email", email);
url.searchParams.set("timeout", "3000");
try {
const res = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
signal: AbortSignal.timeout(4000),
});
return res.ok ? await res.json() : null;
} catch {
return null; // Timed out or unreachable: allow the signup.
}
}
Deno.serve(async (req) => {
const payload = await req.text();
let user: { email?: string };
try {
({ user } = new Webhook(hookSecret).verify(payload, Object.fromEntries(req.headers)) as { user: { email?: string } });
} catch {
return reject("Invalid signature");
}
// Phone and some OAuth signups have no email to check.
if (!user.email) return json({});
const result = await verify(user.email);
if (result?.status === "undeliverable") {
return reject(
result.suggestion
? `That address cannot receive email. Did you mean ${result.suggestion}?`
: "That address cannot receive email. Please check it and try again.",
);
}
if (result?.reason === "disposable") {
return reject("Please sign up with a permanent email address.");
}
return json({});
});Auth hooks have a short time limit, so keep the verify timeout low as shown. An address that takes too long simply passes.
2. Deploy it and set the secrets
Deploy with JWT verification turned off, because Supabase Auth authenticates to this function with the webhook signature instead of a user token. Then generate the hook secret in the Auth Hooks section of the Supabase dashboard, and store it with your API key.
supabase functions deploy before-user-created --no-verify-jwt
supabase secrets set \
BEFORE_USER_CREATED_HOOK_SECRET="v1,whsec_..." \
VERIDOCK_API_KEY="vd_live_..."3. Turn on the hook
In the Supabase dashboard, open Authentication, then Auth Hooks, and add a Before User Created hook of type HTTPS pointing at your function's URL. From then on, every new signup passes through it.
4. Show the message in your signup form
The message from your hook comes back as the signup error, so showing it needs no new code if you already display auth errors.
const { error } = await supabase.auth.signUp({ email, password });
if (error) setFormError(error.message);Reference
Frequently asked questions
Keep reading
Other guides: Next.js · Node.js · Python · React · PHP · Zapier, Make and n8n