auth

Require work emails with Clerk

Check Clerk signups with isBusinessEmail: gate your sign-up form on the server, then classify every new user from the user.created webhook.

isBusinessEmail for Clerk

Clerk handles sign-up and sessions; isBusinessEmail tells you whether the address is a work email and whether the company runs Google Workspace or Microsoft 365. There are two places to connect them, and most teams use both.

Where When it runs Use it to
Your sign-up page, before you hand the email to Clerk Before the account exists Show “please use your work email” and stop obvious personal or disposable signups
The user.created webhook Right after Clerk creates the user Classify every new user (including social sign-ins), store the verdict, restrict or flag accounts

1. A server-side check helper

Keep the secret key on the server. This helper fails open, so a timeout never blocks a signup.

// lib/isbusinessemail.js
export async function checkWorkEmail(email) {
  try {
    const res = await fetch('https://api.isbusinessemail.com/v1/check', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.IBE_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ email }),
      signal: AbortSignal.timeout(2500),
    });
    if (!res.ok) return { recommendation: 'allow', failOpen: true };
    return await res.json();
  } catch {
    return { recommendation: 'allow', failOpen: true };
  }
}

2. Gate your sign-up form

If you use a custom sign-up flow, call a small route of your own before starting the Clerk sign-up, and only continue when it doesn’t say block. Example as a Next.js route handler:

// app/api/precheck/route.js
import { checkWorkEmail } from '@/lib/isbusinessemail';

export async function POST(request) {
  const { email } = await request.json();
  const verdict = await checkWorkEmail(email);
  return Response.json({
    ok: verdict.recommendation !== 'block',
    category: verdict.category ?? null,
    didYouMean: verdict.did_you_mean ?? null,
  });
}

This is a UX gate: a determined user could skip it. The webhook below is your backstop.

3. Classify every user from the webhook

Point a Clerk webhook at your server for the user.created event. Verify the signature first (Clerk delivers webhooks through Svix; follow Clerk’s webhook guide for the verification helper), then classify the primary email.

// after verifying the webhook signature
if (evt.type === 'user.created') {
  const user = evt.data;
  const primary = user.email_addresses.find((e) => e.id === user.primary_email_address_id);
  if (primary) {
    const verdict = await checkWorkEmail(primary.email_address);
    await saveVerdict(user.id, {
      category: verdict.category ?? null,
      recommendation: verdict.recommendation,
      googleWorkspace: verdict.workspace?.google_workspace?.detected ?? null,
      microsoft365: verdict.workspace?.microsoft_365?.detected ?? null,
      tenantId: verdict.workspace?.microsoft_365?.tenant_id ?? null,
    });
  }
}

What saveVerdict does is up to you: write to your own database, or store it in the user’s metadata through Clerk’s Backend API so your frontend can read it. Common follow-ups:

  • block: put the account in a “needs work email” state and ask them to add one.
  • review: allow, but require verification or route to sales.
  • Microsoft 365 detected: pre-select the Microsoft connector in onboarding (guide).

Tips

  • Social sign-ins (Google, Microsoft) skip your form, which is why the webhook matters. A Google sign-in can still be a personal @gmail.com account.
  • Store the verdict once; don’t re-check on every session.
  • Test with personal@test.isbusinessemail.com and m365@test.isbusinessemail.com (test addresses).

Official docs: Clerk documentation. See also: Signup form guide.