Clerk creates the user, then sends a user.created webhook. Check the user there, and ban the account or mark it for verification.
1. Add the webhook endpoint
In the Clerk dashboard, add a webhook endpoint pointing to /api/webhooks/clerk in your app, subscribed to user.created. Copy its signing secret to CLERK_WEBHOOK_SIGNING_SECRET.
2. The handler (Next.js)
// app/api/webhooks/clerk/route.ts
import { verifyWebhook } from "@clerk/nextjs/webhooks";
import { clerkClient } from "@clerk/nextjs/server";
export async function POST(request: Request) {
const event = await verifyWebhook(request);
if (event.type !== "user.created") return new Response("ignored");
const user = event.data;
const email = user.email_addresses?.[0]?.email_address;
if (!email) return new Response("no email");
const result = await fetch("https://veqen-app.com/v1/email/check", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.VEQEN_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ email }),
})
.then((r) => (r.ok ? r.json() : null))
.catch(() => null);
const clerk = await clerkClient();
if (result?.risk.verdict === "refuse") {
await clerk.users.banUser(user.id);
} else {
await clerk.users.updateUserMetadata(user.id, {
privateMetadata: { veqenVerdict: result?.risk.verdict ?? "review" },
});
}
return new Response("ok");
}
The webhook only sees the email. To include the signup IP, run the signup check from your own sign-up route before calling Clerk, or check it at first sign-in.