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.

  1. Check that the package is installed: composer show darvis/livewire-honeypot.
  2. Run php artisan package:discover, then php artisan view:clear.
  3. If composer.json lists the package under extra.laravel.dont-discover, register Darvis\LivewireHoneypot\HoneypotServiceProvider::class in bootstrap/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.