Email validation · Integrations · Clerk
Integration guide
Email validation in Clerk with Trueguard.
In this guide
How do I validate emails in Clerk?
Clerk sends this route a signed event for each new user. It uses the Clerk SDK already in your app and axios, which you add with npm install axios.
app/api/webhooks/clerk/route.ts
import axios from "axios";
import { clerkClient } from "@clerk/nextjs/server";
import { verifyWebhook } from "@clerk/nextjs/webhooks";
import { NextRequest } from "next/server";
const API = "https://api.trueguard.io/v2/email/validation";
type Validation = {
deliverability: { status: "safe" | "risky" | "invalid" | "unknown" };
quality: { isDisposable: boolean };
};
// Returns null when Trueguard gives no verdict, so the account stays.
const validate = async (email: string): Promise<Validation | null> => {
try {
const { data } = await axios.post<Validation>(
API,
{ email },
{
headers: { "X-API-KEY": process.env.TRUEGUARD_KEY! },
signal: AbortSignal.timeout(3000),
},
);
return data;
} catch (err) {
// Log the message only, because the full error holds your API key.
console.error("Trueguard call failed", (err as Error).message);
return null;
}
};
// A disposable inbox can receive Clerk's code, so verified does not clear it.
// An invalid result on an address that Clerk has verified keeps the account.
const shouldBan = ({ deliverability, quality }: Validation, verified: boolean): boolean =>
quality.isDisposable || (deliverability.status === "invalid" && !verified);
export async function POST(req: NextRequest) {
const evt = await verifyWebhook(req).catch((err) => {
console.error("Webhook verification failed", err);
return null;
});
// Not proven to be from Clerk, so nothing is checked or billed.
if (!evt) {
return new Response("Invalid signature", { status: 400 });
}
if (evt.type !== "user.created") {
return new Response("Ignored", { status: 200 });
}
const { id, email_addresses, primary_email_address_id } = evt.data;
const primary = email_addresses.find((e) => e.id === primary_email_address_id);
// No primary email to check, for example a phone-only user.
if (!primary) {
return new Response("No email", { status: 200 });
}
const verified = primary.verification?.status === "verified";
const result = await validate(primary.email_address);
if (result && shouldBan(result, verified)) {
try {
const client = await clerkClient();
await client.users.banUser(id);
} catch (err) {
// A 500 makes Clerk redeliver, which runs one more billed check.
console.error("Ban failed", id, err);
return new Response("Ban failed", { status: 500 });
}
}
return new Response("OK", { status: 200 });
}Add your key and the next new user is checked. 100 validations a month are free, no card.
Get your free API keyOne header, one body field
Set X-API-KEY to your key and POST a JSON body with a single email field. HTTPS only. The response carries the seven signals plus one deliverability status.
Response fields you will branch on
deliverability.statussafe, risky, invalid or unknown. This is the field to branch on.
syntax.isValidRFC 5322 format check. Deterministic, so a false here is always a typo or a malformed address.
quality.isDisposableTrue for throwaway providers. Usually a hard block at signup.
deliverability.isCatchallTrue when the domain accepts any address. The status is then risky, unless a second check confirmed or ruled out the mailbox.
quality.isRoleinfo@, support@, sales@. Route these differently rather than rejecting them.
Run it once before you write any code.
The same endpoint the snippet above calls. 10 free checks a day, no key needed.
Or try
Where does the email check go in Clerk?
Clerk's docs list no synchronous hook that can reject a signup server-side (read 2026-11-09). Webhooks are asynchronous, so the check goes in a user.created webhook, and it runs after the account exists.
A check in your signup form
Form code runs in the browser, so treat any check there as advisory. Your Trueguard key is a secret and cannot ship with it. Users created through the Backend API never touch the form.
A user.created webhook
Clerk documents this event for users who register in your app and users created in the Dashboard or Backend API. A session can already be active when it arrives, so the handler bans the user, and a ban revokes all their sessions.
What the endpoint returns
{
"email": "founder@yourcompany.com",
"rawEmail": "founder@yourcompany.com",
"syntax": { "isValid": true },
"deliverability": {
"status": "safe",
"isSmtpValid": true,
"isMxValid": true,
"isCatchall": false,
"isInboxFull": false,
"isDeliverable": true,
"isDisabled": false,
"mxRecords": ["mx1.yourcompany.com", "mx2.yourcompany.com"]
},
"domain": {
"name": "yourcompany.com",
"age": 2841,
"isLive": true,
"isRisky": false
},
"quality": {
"isDisposable": false,
"isFree": false,
"isRole": false,
"isSubaddress": false
}
}How do I connect the webhook in Clerk?
Four steps. Until the signing secret is set, verifyWebhook throws and the route returns 400.
- 01
Add the route.
Runnpm install axiosand paste the handler above./api/webhooks(.*)must stay public. By defaultclerkMiddleware()protects no routes, so only exclude it if you added auth checks. See Clerk's syncing guide. - 02
Create the endpoint.
In Dashboard > Webhooks, point an endpoint athttps://your-domain.com/api/webhooks/clerk, subscribe it touser.createdonly, and copy the signing secret. - 03
Set three secrets.
Your Trueguard key comes from the Trueguard dashboard.# .env.local, keep it out of git CLERK_WEBHOOK_SIGNING_SECRET="whsec_..." CLERK_SECRET_KEY="your_clerk_secret_key" TRUEGUARD_KEY="your_api_key" - 04
Test both paths.
Sign up with a real address and confirm the user stays active. Sign up with a disposable address and confirm the user is banned. If every delivery returns 400, check the signing secret first.
What does Clerk's email code prove, and who should the webhook ban?
Clerk's email code shows that someone read that inbox while the code was valid, which is 10 minutes. A temporary inbox can do that too, so disposable addresses are the webhook's main catch. A sign-up that requires verification is not complete until the address is verified, so an address that cannot receive mail should stop there. It can still reach your user table when you create the user through the Backend API or Dashboard. That is our reading of Clerk's docs on 2026-11-09. They do not state when user.created fires relative to verification, so the code reads the address's own verification status and is correct either way.
| Signal | Hook action | Why |
|---|---|---|
quality.isDisposable (any status) | Ban | A temporary inbox. It can receive Clerk's code, so verification does not stop it. |
invalid, address not verified | Ban | Trueguard says it cannot receive mail and Clerk holds no proof that it can. Malformed addresses land here too. |
invalid, address verified | Allow | Clerk's verification status takes priority over a single API result. |
safe | Allow | The mailbox can receive mail. |
safe with isCatchall: true | Allow | Trueguard's advanced catch-all detection validated the mailbox on a catch-all domain. |
risky with isCatchall: true | Allow | The domain accepts all mail and the mailbox was not validated. Real people use these domains. |
risky, high-risk TLD | Allow | A weak reason to ban an account on its own. |
unknown | Allow | The mail server gave no clear answer. |
quality.isRole | Allow | Never changes the status. team@ can be a paying customer. See role addresses. |
quality.isFree | Allow | Never changes the status. Personal addresses are normal signups. |
quality.isSubaddress | Allow | An alias on a mailbox. |
| Trueguard error or no answer in 3 s | Allow and log | Your quota or an outage should not cost you an account. |
| No primary email | Allow, no API call | No validation used. |
Event other than user.created | Ignore, 200 | Subscribe to this one event and it should not occur. |
| Signature check fails | Reject, 400 | Not proven to come from Clerk. Nothing billed. |
banUser throws | Return 500 | Clerk redelivers, and the redelivery can finish the ban. |
How do I avoid blocking real users?
Fail open on anything that is not a verdict.
Ban two cases only.
Run imports before you connect this.
Put the message at the form.
What should a Clerk webhook return?
Clerk retries until your endpoint returns a 2xx or the retries run out, and an event can reach you more than once. Read on 2026-11-09. Each delivery of a user.created event for an address is one more Trueguard check.
// Bad signature: no Trueguard call, nothing billed.
return new Response("Invalid signature", { status: 400 });
// Address passed, or Trueguard failed: Clerk stops retrying.
return new Response("OK", { status: 200 });
// banUser threw: Clerk retries until a 2xx or the retries run out.
return new Response("Ban failed", { status: 500 });Status
What Clerk does
200Clerk stops retrying. Sent when the ban succeeds, the address passes, or Trueguard fails.
400Bad or missing signature. Nothing is checked or billed. Clerk retries 4xx responses too.
500banUser threw. Clerk retries, and each retry runs one more billed check.
NoneNo status code counts as a failure and is retried.
What does Clerk block on its own?
From Clerk's restriction, disposable, subaddress and Protect docs and its pricing page, read on 2026-11-09.
Disposable protection.
Subaddress protection.
Risky domains.
Users you create yourself.
What does each Clerk signup cost?
Trueguard checks run on user.created, so logins cost nothing. The free plan covers 100 signups a month. Standard is $49.99 for 50,000, which is $0.001 per signup at full use. At 2,000 signups a month that is $49.99 / 2,000 = about $0.025 each. Past 50,000 it is $0.001 per check, so 80,000 signups cost $49.99 + 30,000 × $0.001 = $79.99. Every check counts, banned addresses included, and each redelivered event is one more check. A 20,000 user import is 20,000 checks if the webhook is live.
Standard
Popular$49.99 / month
50,000 validations / month
Overage: $0.001 per validation
Get your API keyFrequently asked questions
The full field reference lives in the API documentation.
How do I block disposable emails in Clerk?
Use Trueguard in a user.created webhook and ban when quality.isDisposable is true. The route takes one Next.js file, and 100 checks a month are free. Clerk also has a disposable setting on every plan, off by default, read 2026-11-09.
Does Clerk have a before-signup hook?
Clerk's docs list no synchronous hook that can reject a signup server-side, read 2026-11-09. Webhooks are asynchronous.
Does Clerk's email code make this check unnecessary?
The code shows someone read that inbox. A temporary inbox can receive one, so the disposable check still has work to do. Timing is our reading of the docs.
Should I ban or delete the user?
Ban. Clerk documents that a ban revokes all sessions, and its delete page does not mention sessions. Clerk bills retained users, never total users in the table.
Can a banned user keep using the app for a moment?
A session can be active until the handler finishes. Sessions are then revoked. Clerk's session token lasts 60 seconds, so on our reading an issued token may validate until it expires. Clerk does not describe this case.
What happens if Trueguard is slow or down?
The account stays. The handler waits 3 seconds, logs the error and returns 200, so Clerk does not redeliver.
Should I ban risky or catch-all addresses?
Only when disposable. Trueguard's advanced catch-all detection can validate a mailbox on a catch-all domain: validated is safe with isCatchall true, not validated is risky. The handler allows both.
Does every Clerk signup method fire user.created?
Clerk documents app registration, Dashboard and Backend API creates. Its docs do not list OAuth, magic link or import method by method. Test the methods you use.
Can I show an error on my signup form?
In a custom flow you can call your own route before signUp, keeping the key on the server. That is a usability step, the webhook stays the enforcement, and it uses a second check per signup. Clerk's disposable setting also shows an error at signup that the address is not allowed.
Does every login use a validation?
No. The route acts only on user.created. No primary email means no call. Banned addresses count, as does each redelivery.
Check every new Clerk signup with Trueguard.
100 validations a month, free, no card. Add the route, set your key, and the next new user is checked.
Get your free API keyNo credit card required.

