Troubleshooting
Each section is a symptom, its causes and the fix. The messages are quoted exactly as the package and Laravel show them, so you can search for them.
Start by logging the SpamBlocked event. Its reason tells you which check refused the submission.
Real visitors get “Spam detected.”
Spam detected. means one of two things: the bait field was filled in or missing (reason field_filled), or the start time or token was invalid (reason invalid_payload).
| Cause | Fix |
|---|---|
Plain form, every submit fails, package version 1.5.1 or older. Laravel’s ConvertEmptyStringsToNull middleware turns the empty bait into null, and those versions count null as “not submitted” | Upgrade to 1.6.0 or higher. See Version 1.5.1 or older |
Plain form: <x-honeypot /> is outside the <form> tag, or a script sends only some of the fields, so the bait and hp_token never arrive | Move <x-honeypot /> inside the form. In a JavaScript form, send the bait and hp_token along |
Plain form: APP_KEY was rotated, and forms that were already open carry a token signed with the old key | Put the old key in APP_PREVIOUS_KEYS in .env |
Livewire form posts to a controller, or the controller of a Livewire form calls HoneypotService::validate(). A Livewire form has no hp_token | Call $this->validateHoneypot() in the component. Use validate() only for plain forms |
You render the bait input yourself, under a name or label that browser autofill recognises, such as the default hp_website with a “Website” label. Autofill fills it for real visitors | Use <x-honeypot />, or set HONEYPOT_FIELD_NAME to a neutral word and use a neutral label. Check the form with the autofill test |
A published view or translation from before 1.2.0 still uses the name hp_website and the label “Website (leave empty)” | Delete resources/views/vendor/livewire-honeypot and lang/vendor/livewire-honeypot, or publish them again. See Changing the HTML |
Your honeypot may be blocking real visitors explains the autofill problem.
Real visitors get “Form submitted too quickly.”
| Cause | Fix |
|---|---|
The form is short, and a visitor with autofill finishes it within minimum_fill_seconds (default 5) | Lower HONEYPOT_MINIMUM_FILL_SECONDS, for example to 2. For one plain form: validate($request->all(), minimumSeconds: 2) |
Livewire: a visitor sends a second message right after the first. resetHoneypot() restarted the timer | This is intended. Lower the minimum if it gets in the way |
| A test submits at once | Travel in time or set the minimum to 0. See Testing your forms |
Visitors get “This form has expired. Please try again.”
Only plain forms expire, after maximum_fill_seconds (default 86400, one day).
| Cause | Fix |
|---|---|
| The page was open for longer than a day | Nothing. The visitor reloads and submits again |
A full-page cache or CDN serves the same HTML, with the same token, for longer than maximum_fill_seconds | Exclude pages with a form from the cache, or build the form in Livewire. HONEYPOT_MAXIMUM_FILL_SECONDS=0 turns expiry off, but see Page caching |
Nothing is blocked
Test it first: submit the form within five seconds. You should see “Form submitted too quickly.”.
| Cause | Fix |
|---|---|
validateHoneypot() (Livewire) or HoneypotService::validate() (controller) is never called. The trait and <x-honeypot /> alone check nothing | Add the call in the method that handles the submit, before you process the data |
minimum_fill_seconds is 0, in .env or in a published config/livewire-honeypot.php | Set it back to 5 and run php artisan config:clear |
| Plain form on a cached page: the token is old, so the time check always passes | Exclude the page from the cache. See Page caching |
Livewire: <x-honeypot wire:model="..."> binds the bait to another property, while validateHoneypot() without arguments reads hp_website | Remove the attribute, or pass the same path: validateHoneypot(model: '...'). See Binding the bait to another property |
Livewire: resetHoneypot() is not called after a successful submit, so later submits from the same page skip the wait | Call $this->resetHoneypot() after processing |
| The spam comes from a person, or from a bot that runs a real browser and waits | A honeypot does not stop that. See What it does not stop |
A changed setting has no effect
| Cause | Fix |
|---|---|
The config is cached, so .env is not read | php artisan config:clear, or php artisan config:cache again |
A published config/livewire-honeypot.php has a hard-coded value that wins over the package default | Edit the published file, or delete it to use .env and the defaults |
You set token_min_length | It was removed in 1.4.0 and is ignored |
You changed field_name for a Livewire form | Livewire forms always use hp_website. field_name is for plain forms |
The hidden field is visible
Visitors see a field labelled “Leave this field empty”. Your Content Security Policy blocks the inline style attribute that hides it. Give the component a nonce: see Content Security Policy.
The error message shows twice, or not at all
| Symptom | Cause and fix |
|---|---|
| Twice | The view has its own @error('hp_website'). Remove it; <x-honeypot /> shows the error |
| Not at all, plain form | The form page is rendered outside the web middleware group, so the view has no $errors variable. Put the form page and the POST route in routes/web.php |
Not at all, custom field_name | You read the error under hp_website while plain forms report it under field_name. Let <x-honeypot /> show it |
| Not at all, published view | The view was published before 1.1.0 and has no error block. Delete or republish it |
Cannot update locked property: [hp_started_at]
Livewire throws CannotUpdateLockedPropertyException with this message, or with [hp_token].
| Cause | Fix |
|---|---|
A test calls ->set('hp_started_at', ...) or ->set('hp_token', ...) | Use $this->travel(10)->seconds(). See Testing your forms |
Your own view binds hp_started_at or hp_token with wire:model | Remove the binding. Only the bait, hp_website, is bound, and <x-honeypot /> does that |
| A bot tried to change the start time | Nothing. This is the protection working |
The honeypot of … has no start time
Since 1.6.0 the package throws a LogicException: “The honeypot of App\Livewire\Forms\ContactFormData has no start time, because Livewire only runs mountHasHoneypot() on a component. Use the HasHoneypot trait on the Livewire component, not on a form object, and call $this->validateHoneypot() there.”
The HasHoneypot trait is on a Livewire form object, or on another class that is not a component. Livewire never sets the start time there. Move the trait and the validateHoneypot() call to the component: see Form objects. Older versions answered every submit of such a form with “Spam detected.”.
No application encryption key has been specified.
Laravel’s MissingAppKeyException. The package signs tokens with APP_KEY and refuses to sign with an empty key. Run php artisan key:generate, then php artisan config:clear.
Unable to locate a class or view for component [honeypot].
Laravel does not know the <x-honeypot /> component, so the service provider is not loaded.
- Check that the package is installed:
composer show darvis/livewire-honeypot. - Run
php artisan package:discover, thenphp artisan view:clear. - If
composer.jsonlists the package underextra.laravel.dont-discover, registerDarvis\LivewireHoneypot\HoneypotServiceProvider::classinbootstrap/providers.php.
spatie/laravel-honeypot also registers a Blade component named <x-honeypot />. Install one of the two packages, not both.
Still stuck?
Open an issue with the package, Laravel and Livewire versions and the reason of the SpamBlocked event. Found a way around the honeypot? Report it privately, as described in the security policy.