Block disposable email signups in Clerk
Use Clerk's `restrictions` API or a webhook on `user.created` to reject disposable signups before they become full Clerk users. Two patterns depending on whether you want to block at the form or at the API.
The code
// app/api/webhooks/clerk/route.ts — runs on user.created
// Configure in Clerk dashboard: Webhooks → +endpoint → user.created
import { Webhook } from 'svix';
import { clerkClient } from '@clerk/nextjs/server';
async function isDisposable(email: string) {
const r = await fetch(
`https://api.checkdisposable.email/v1/check?email=${encodeURIComponent(email)}`,
{ headers: { Authorization: `Bearer ${process.env.CDE_KEY!}` } }
);
if (!r.ok) return false;
return (await r.json()).is_disposable === true;
}
export async function POST(req: Request) {
const payload = await req.text();
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!);
const evt = wh.verify(payload, Object.fromEntries(req.headers)) as any;
if (evt.type === 'user.created') {
const email = evt.data.email_addresses[0]?.email_address;
if (email && (await isDisposable(email))) {
// Roll the user back — Clerk's only async option after creation.
const client = await clerkClient();
await client.users.deleteUser(evt.data.id);
}
}
return new Response('ok');
}Notes
- Pre-signup block (cleaner)
- For stricter UX, validate the email in your sign-up form before calling `signUp.create()` and refuse there. The webhook is a safety net for OAuth-based signups (Google, Apple) where you can't intercept the address.
- Webhook secret
- Copy the signing secret from the Clerk dashboard into `CLERK_WEBHOOK_SECRET`. Without it, Svix verification fails and your endpoint accepts spoofed requests.
- Why delete instead of block
- Clerk does not expose a "reject during creation" hook. Deletion happens within seconds of the user being created and before they can sign in — they never see a session.
Get a free API key
500 checks/month, no credit card. No credit card. 30 seconds.
Sign up free →