Integrations · Updated 2026-10-20

Screen Clerk signups with a webhook

Check every new Clerk user with a user.created webhook, and ban or flag throwaway and bot accounts automatically.

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.