api.
Real-time VerificationFrameworks

Laravel

Getting Started

Laravel's elegant syntax and built-in HTTP client make integrating user.cleaning a breeze. We'll use Laravel's Http facade, which provides a clean and expressive way to make HTTP requests.

First, store your API key securely in your environment file:

.env
USER_CLEANING_API_KEY=your-api-key

Then register it in your services configuration so it's easily accessible throughout your application:

config/services.php
'user_cleaning' => [
    'api_key' => env('USER_CLEANING_API_KEY'),
],

If you don't have an API key yet, check out our Quickstart guide to generate one.

IP Forwarding for Rate Limiting

For user.cleaning's rate limiting to work correctly, you need to forward the real client IP with each request. Add this helper function to extract the client IP:

app/Helpers/helpers.php
function getClientIp(): string
{
    return request()->header('X-Forwarded-For')
        ? explode(',', request()->header('X-Forwarded-For'))[0]
        : (request()->header('X-Real-IP') ?? request()->ip() ?? 'unknown');
}

Production: If you're behind nginx, Cloudflare, or a load balancer, X-Forwarded-For is set automatically.

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 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.

Creating the Validation Function

Let's create a helper function that communicates with the user.cleaning API:

app/Helpers/helpers.php
<?php

use Illuminate\Support\Facades\Http;

function checkEmail(string $email, string $clientIp): array
{
    try {
        $response = Http::timeout(5)
            ->withHeaders([
                'X-API-Key' => config('services.user_cleaning.api_key'),
                'X-Forwarded-For' => $clientIp,
                'X-Real-IP' => $clientIp,
            ])
            ->post('https://api.user.cleaning/v1/external-api-requests/check-email', [
                'email' => $email
            ]);

        if ($response->status() === 403) {
            return [
                'category' => 'banned',
                'message' => $response->json('detail', 'Access denied')
            ];
        }

        return $response->json();
    } catch (\Exception $e) {
        return ['category' => 'white']; // Fail open
    }
}

The API responds with a JSON object containing a category field that tells you how to handle the email address.

Integration Approaches

Validating in Controllers

The most straightforward approach is to call the validation function directly in your controller:

app/Http/Controllers/AuthController.php
public function register(Request $request)
{
    $clientIp = getClientIp();
    $result = checkEmail($request->email, $clientIp);
    
    if ($result['category'] === 'banned') {
        abort(403, $result['message']);
    }
    
    if ($result['category'] === 'black') {
        abort(400, 'Disposable emails are not allowed');
    }
    
    if ($result['category'] === 'grey') {
        // Suspicious email — let them in but require phone verification
        $user = User::create([
            'email' => $request->email,
            'password' => Hash::make($request->password),
            'requires_phone_verification' => true,
        ]);
        
        return response()->json([
            'message' => 'Please enter your phone number for verification',
            'user_id' => $user->id
        ], 201);
    }
    
    // 'white' — all good
    $user = User::create([
        'email' => $request->email,
        'password' => Hash::make($request->password),
        'email_verified_at' => now(),
    ]);
    
    return response()->json(['user_id' => $user->id], 201);
}

This approach works well when you have just a few routes that need email validation and want direct control over the logic.

Creating a Custom Validation Rule

Laravel's custom validation rules are perfect for creating reusable, declarative validation logic:

app/Rules/NotDisposableEmail.php
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class NotDisposableEmail implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $clientIp = getClientIp();
        $result = checkEmail($value, $clientIp);
        
        if ($result['category'] === 'banned') {
            $fail($result['message']);
        }
        
        if ($result['category'] === 'black') {
            $fail('Please use a permanent email address. Disposable emails are not allowed.');
        }
    }
}

Now you can use this rule in any Form Request:

app/Http/Requests/RegisterRequest.php
<?php

namespace App\Http\Requests;

use App\Rules\NotDisposableEmail;
use Illuminate\Foundation\Http\FormRequest;

class RegisterRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'email' => ['required', 'email', 'unique:users', new NotDisposableEmail()],
            'password' => ['required', 'min:8', 'confirmed'],
        ];
    }
}

Laravel will automatically reject disposable emails with your custom error message, and the validation errors will be returned in the standard Laravel format.

Understanding the Results

The category field can have three possible values:

  • black — This is a disposable or temporary email address that should be blocked
  • grey — Suspicious patterns were detected (like alias chains or plus addressing), consider requiring phone verification
  • white — The email looks legitimate and can be accepted without restrictions

For a complete description of the output data structure and all available fields, see our Categories Reference.

On this page