api.
Real-time VerificationAuth providers

Better Auth

If you're using Better Auth for authentication, you'll appreciate how naturally user.cleaning integrates with its hooks system. Validate emails before accounts are created, keeping your user base clean from day one.

Getting Started

Before integrating user.cleaning with Better Auth, you'll need:

  • A project with Better Auth already configured
  • Your user.cleaning API key from the Quickstart guide

IP Forwarding Setup

For user.cleaning's rate limiting and IP-based fraud detection to work correctly, your application must forward real client IPs via X-Forwarded-For headers.

Production environments: If you're using nginx, Cloudflare, AWS ALB, or similar reverse proxies, this is already configured automatically - no action needed.

Development/testing: Without a reverse proxy forwarding X-Forwarded-For, the integration will see your server's IP instead of actual client IPs. This means all requests appear to come from the same IP, and after 5 disposable email attempts, all registrations will be blocked until the IP is unbanned.

If you see container IPs in your logs or experience unexpected registration blocks, verify your reverse proxy is forwarding client IPs correctly. You can unban blocked IPs at API Settings and configure number of registration attempts before block or disable it completely at Color Configuration.

Adding Email Validation

Add the following imports and hooks configuration to your existing Better Auth setup:

// lib/auth.ts
import { betterAuth } from "better-auth";
import { createAuthMiddleware, APIError } from "better-auth/api";

export const auth = betterAuth({
  // ... your existing database and emailAndPassword config

  hooks: {
    before: createAuthMiddleware(async (ctx) => {
      if (ctx.path !== "/sign-up/email") return;

      const email = ctx.body?.email;
      
      // Extract user's real IP from the request
      const userIp = ctx.headers?.get("x-forwarded-for")?.split(",")[0].trim()
        || ctx.headers?.get("x-real-ip")
        || "unknown";

      try {
        const response = await fetch(
          `https://api.user.cleaning/v1/external-api-requests/check-email?email=${encodeURIComponent(email)}`,
          {
            method: "POST",
            headers: {
              "X-API-Key": process.env.USER_CLEANING_API_KEY!,
              "X-Forwarded-For": userIp,  // Forward the real user IP
              "X-Real-IP": userIp,
            },
          }
        );

        if (!response.ok) {
          if (response.status === 403) {
            throw new APIError("BAD_REQUEST", {
              message: "Too many disposable email attempts. Please try again later.",
            });
          }
          // Service unavailable - allow signup to proceed
          console.warn("Email validation service returned", response.status);
          return;
        }

        const result = await response.json();

        if (result.category === "black") {
          throw new APIError("BAD_REQUEST", {
            message: "Disposable email addresses are not allowed.",
          });
        }

        // Optional: also block grey emails (aliases, plus-addressing)
        // if (result.category === "grey") {
        //   throw new APIError("BAD_REQUEST", {
        //     message: "Please use your primary email address.",
        //   });
        // }
      } catch (error) {
        if (error instanceof APIError) throw error;
        // Network error - allow signup to proceed
        console.warn("Email validation skipped:", error.message);
        return;
      }
    }),
  },
});

Environment Variables

Add your API key to your environment:

USER_CLEANING_API_KEY=your_api_key_here

Understanding Validation Results

The API categorizes emails into three groups. See the Categories Reference for detailed explanations.

Black (Block): Disposable email domains that should never be allowed.

Grey (Caution): Suspicious patterns like aliases or plus-addressing. By default allowed, but you can uncomment the grey validation block to block them.

White (Allow): Legitimate email addresses that pass validation.

Rate Limiting & IP Banning

user.cleaning automatically tracks abusive behavior. After multiple attempts to use disposable emails from the same IP, that IP gets temporarily banned (HTTP 403). The integration handles this gracefully by showing users a rate limit message.

This protection works only when real client IPs are forwarded correctly (see IP Forwarding Setup above).

Error Handling

The integration uses a "fail open" approach — if the validation service is unavailable, signups proceed normally. This ensures your authentication flow isn't blocked by external service issues.

When validation succeeds and finds a disposable email, users see:

Disposable email addresses are not allowed.

Customize this message in the APIError to match your application's tone.

Testing Your Integration

  1. Try signing up with a disposable email (e.g., test@tempmail.com)
  2. You should see the error message immediately
  3. Try a legitimate email - signup should proceed normally
  4. Check server logs to confirm the real client IP is being logged

On this page