Clerk is one of the most popular modern authentication platforms for Next.js applications. However, out of the box, Clerk allows users to register with any valid syntax email, including temporary burner domains like Mailinator, GuerrillaMail, and TempMail.
By combining Clerk Webhooks with MailCheck API, you can automatically inspect every new registration in sub-50ms and instantly delete or quarantine users who register with disposable addresses.
1. Why Use Webhook Interception?
Clerk signups often happen through hosted UI components (<SignUp />). Intercepting the user.created event on your backend webhook offers several advantages:
- Zero Frontend Latency: The signup modal renders instantly without waiting for custom client-side hooks.
- Tamper-Proof Enforcement: Bad actors cannot bypass client-side JavaScript checks.
- Automated Cleanup: The backend deletes the user from Clerk and your primary database before any welcome emails or free credits are dispatched.
2. The Interception Architecture
graph LR
A["User Signs Up via Clerk UI"] --> B["Clerk Dispatches user.created Webhook"]
B --> C["Next.js Route Handler (/api/webhooks/clerk)"]
C --> D["Verify Svix Signature"]
D --> E["Query MailCheck API (Sub-50ms)"]
E -->|Is Disposable| F["Purge User via Clerk Backend SDK"]
E -->|Is Legitimate| G["Provision App Workspace & Trial"]
3. Environment Variables & Dependencies
First, install the necessary dependencies in your Next.js project:
npm install svix @clerk/nextjs
Add your secrets to .env.local:
CLERK_SECRET_KEY=sk_test_...
CLERK_WEBHOOK_SECRET=whsec_...
RAPIDAPI_KEY=your_mailcheck_rapidapi_key
4. Complete Next.js 14/15 Route Handler
Create app/api/webhooks/clerk/route.ts:
import { Webhook } from 'svix';
import { headers } from 'next/headers';
import { WebhookEvent } from '@clerk/nextjs/server';
import { clerkClient } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;
if (!WEBHOOK_SECRET) {
throw new Error('Please add CLERK_WEBHOOK_SECRET to .env');
}
// 1. Get the Svix headers for signature verification
const headerPayload = headers();
const svix_id = headerPayload.get('svix-id');
const svix_timestamp = headerPayload.get('svix-timestamp');
const svix_signature = headerPayload.get('svix-signature');
if (!svix_id || !svix_timestamp || !svix_signature) {
return new NextResponse('Error: Missing svix headers', { status: 400 });
}
const payload = await req.json();
const body = JSON.stringify(payload);
// 2. Verify Svix signature
const wh = new Webhook(WEBHOOK_SECRET);
let evt: WebhookEvent;
try {
evt = wh.verify(body, {
'svix-id': svix_id,
'svix-timestamp': svix_timestamp,
'svix-signature': svix_signature,
}) as WebhookEvent;
} catch (err) {
console.error('Error verifying Clerk webhook:', err);
return new NextResponse('Error: Invalid signature', { status: 400 });
}
// 3. Handle user.created event
if (evt.type === 'user.created') {
const { id: userId, email_addresses } = evt.data;
const primaryEmail = email_addresses?.[0]?.email_address;
if (!primaryEmail) {
return NextResponse.json({ message: 'No email found' }, { status: 200 });
}
try {
// 4. Validate email with MailCheck API
const checkRes = await fetch('https://fadsync-email-validation.p.rapidapi.com/v1/check', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-RapidAPI-Key': process.env.RAPIDAPI_KEY!,
'X-RapidAPI-Host': 'fadsync-email-validation.p.rapidapi.com'
},
body: JSON.stringify({ email: primaryEmail })
});
const checkData = await checkRes.json();
// 5. Block & Delete if disposable or high-risk
if (checkData.is_disposable || checkData.recommendation === 'BLOCK') {
console.warn(`[Anti-Fraud] Disposable email detected (${primaryEmail}). Deleting Clerk user ${userId}...`);
// Delete user immediately to prevent free credit consumption
await clerkClient.users.deleteUser(userId);
return NextResponse.json({
success: false,
status: 'BLOCKED',
reason: 'Disposable email addresses are strictly prohibited.'
}, { status: 200 });
}
console.log(`[Auth] Valid user signup approved for ${primaryEmail}`);
return NextResponse.json({ success: true, status: 'APPROVED' }, { status: 200 });
} catch (apiError) {
console.error('MailCheck verification error:', apiError);
return NextResponse.json({ error: 'Validation failed' }, { status: 500 });
}
}
return NextResponse.json({ received: true }, { status: 200 });
}
5. Testing with the Clerk Dashboard
- In the Clerk Dashboard, navigate to Configure $ ightarrow$ Webhooks $ ightarrow$ Add Endpoint.
- Set the URL to
https://your-domain.com/api/webhooks/clerk. - Subscribe to the
user.createdevent. - Copy the Signing Secret to your
CLERK_WEBHOOK_SECRETenvironment variable. - Trigger a test registration with a temporary email (e.g. from
10minutemail.com) and verify in your server logs that the user is immediately purged!
