natbox.io

CaptchaBox / Docs / Laravel

Add a captcha to Laravel forms

Last updated 6 November 2026 · By Mohammad AlSaraf

About 5 minutes. Laravel 10 or newer; tested with Laravel 13.

You will add one validation rule, new CaptchaBox, that you can put on any form request. It uses Laravel's HTTP client; no package to install.

1. Get your keys

Sign in to the CaptchaBox client area (a free account is enough) and add your site with its domain. You get a site key for the page and a secret for your server. Keep the secret out of your HTML and out of git: put it in an environment variable.

Add them to .env:

CAPTCHABOX_SITE_KEY=your-site-key
CAPTCHABOX_SECRET=your-secret

and to the array in config/services.php:

    'captchabox' => [
        'site_key' => env('CAPTCHABOX_SITE_KEY'),
        'secret' => env('CAPTCHABOX_SECRET'),
    ],

2. Create the validation rule

Save this as app/Rules/CaptchaBox.php:

<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Http;

/**
 * CaptchaBox validation rule (Laravel 10+).
 * Usage: $request->validate(['captchabox-response' => ['required', new CaptchaBox]]);
 */
class CaptchaBox implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! is_string($value) || $value === '' || ! $this->passes($value)) {
            $fail('The captcha check failed. Please try again.');
        }
    }

    /** Asks CaptchaBox whether the token is valid. Each token works once, for 5 minutes. */
    private function passes(string $token): bool
    {
        try {
            return Http::asForm()->timeout(10)->post('https://captcha.natbox.io/v1/siteverify', [
                'secret' => config('services.captchabox.secret'),
                'response' => $token,
            ])->json('success') === true;
        } catch (\Throwable $e) {
            report($e);
            return false;
        }
    }
}

3. Add the widget to your Blade view

<!doctype html>
<meta charset="utf-8">
<title>Contact</title>
<script src="https://captcha.natbox.io/v1/widget.js" async defer></script>

@if (session('sent'))
    <p id="result">Thanks, your message was sent.</p>
@endif
@error('captchabox-response')
    <p id="error">{{ $message }}</p>
@enderror

<form method="post" action="{{ route('contact.send') }}">
    @csrf
    <input name="email" type="email" value="{{ old('email') }}" placeholder="Your email" required>
    <div class="captchabox" data-sitekey="{{ config('services.captchabox.site_key') }}"></div>
    <button>Send</button>
</form>

4. Validate it

Add the rule to the field captchabox-response, in a controller, a route or a Form Request:

<?php

use App\Rules\CaptchaBox;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::view('/contact', 'contact');

Route::post('/contact', function (Request $request) {
    $request->validate([
        'email' => ['required', 'email'],
        'captchabox-response' => ['required', new CaptchaBox],
    ], [
        'captchabox-response.required' => 'Please wait for the captcha check to finish.',
    ]);

    // The visitor passed: send the email, save the message, etc.
    return back()->with('sent', true);
})->name('contact.send');

In a Form Request, put the same line in rules(): 'captchabox-response' => ['required', new CaptchaBox],

Trying it out? The test keys cb_test_pass (site key) and cbs_test_pass (secret) always pass, on any domain including localhost. Swap in your own keys from the client area before going live.

Laravel captcha questions

Does it work with Livewire?

Yes. Put the widget inside your Livewire form with wire:ignore on its wrapper, read the token with captchabox.getResponse() before calling your action, check it with the same rule, and call captchabox.reset() after each submit, because a token works once.

Can I use it on the Breeze or Jetstream login?

Yes. Add the widget to the login view and the rule to the login request's rules(). Consider letting logins through when CaptchaBox cannot be reached at all, so an outage never locks users out.

How do I test my forms?

Fake the call in feature tests, so they do not reach the internet: Http::fake(['captcha.natbox.io/*' => Http::response(['success' => true])]), then post any value in captchabox-response.

Other platforms: all CaptchaBox docs. Questions? Open a ticket, we reply within 24 hours.