Auth Integrations & Webhooks

How to Block Disposable Emails in Clerk Webhooks (Next.js 14/15 App Router)

Learn how to verify Svix webhook signatures, evaluate new Clerk signups with MailCheck API, and automatically purge temporary burner accounts before they access your app.

2026-08-02
5 min read
MailCheck Engineering

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

  1. In the Clerk Dashboard, navigate to Configure $ ightarrow$ Webhooks $ ightarrow$ Add Endpoint.
  2. Set the URL to https://your-domain.com/api/webhooks/clerk.
  3. Subscribe to the user.created event.
  4. Copy the Signing Secret to your CLERK_WEBHOOK_SECRET environment variable.
  5. 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!

Test Live Validation Endpoint

Try the API directly in real-time before implementing the guide instructions.

LIVE VALIDATION ENGINE
mailcheck verify
Enter an email address above to test the cascading validation logic in real-time.