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.comaccount. - Store the verdict once; don’t re-check on every session.
- Test with
personal@test.isbusinessemail.comandm365@test.isbusinessemail.com(test addresses).
Official docs: Clerk documentation. See also: Signup form guide.