Skip to content

Reset the captcha after AJAX submissions, public JS events, log failed verifications (1.24.0) - #67

Merged
forgetso merged 1 commit into
mainfrom
feat/js-events-ajax-reset-verify-logging
Oct 29, 2026
Merged

forgetso merged 1 commit into
mainfrom
feat/js-events-ajax-reset-verify-logging

Conversation

@forgetso

Copy link
Copy Markdown
Member

Why

A Procaptcha token can only be verified once: the provider marks it as server-checked and returns USER_ALREADY_VERIFIED on a second verify. Forms that submit over AJAX keep the same widget on the page. So after a failed submission (for example, another field was invalid), the next attempt sends a spent token and is rejected.

Changes

  • Reset after AJAX submissions. ajaxFormsReset.ts dispatches prosopo-procaptcha:reset on the submitted form after each response:

    Plugin Hook
    Contact Form 7 wpcf7submit (native)
    WPForms wpformsAjaxSubmitFailed (on success the form is replaced)
    Fluent Forms fluentform_submission_success / _failed
    Formidable frmFormErrors (on success the form is replaced)
    Ninja Forms nfFormSubmitResponse
    JetFormBuilder jet-form-builder/ajax/on-success / processing-error / on-fail
    User Registration user_registration_frontend_after_ajax_complete
    WooCommerce classic checkout checkout_error
    Elementor Pro, Everest Forms, Beaver Builder ajaxComplete, matched on the AJAX action plus form/node id
    Blocksy newsletter subscribe-loading class removed (no events)

    Not covered:

    • Gravity Forms and Divi re-render the form, so a fresh widget appears anyway.
    • Spectra hides the form after any response.
    • Bricks, Jetpack Forms, WooCommerce blocks checkout and MemberPress: no reliable event found. Third-party code can dispatch the reset event itself.
  • Widget reset. Each widget keeps the ID returned by procaptcha.render() and listens for the reset event on document, in the capture phase. It resets when the event target is the document or contains the widget. If the service script is older and has no reset(), it logs a warning.

  • Public JS events.

    • prosopo-procaptcha:filled (detail {token}) and prosopo-procaptcha:cleared are dispatched on the widget and bubble up.
    • Dispatching prosopo-procaptcha:reset resets the widget.
    • _prosopo-procaptcha__filled is still dispatched for existing code.
    • Our own integrations now use the constants in widgetEvents.ts.
  • Verification logging.

    • When WP_DEBUG is on, failed verifications go to the debug log with the HTTP code, status, reason and error. The token and secret are never logged.
    • The new action prosopo/procaptcha/verification_result( bool $verified, array $body ) exposes score, reason and sessionId.
  • Version bumped to 1.24.0, with a changelog entry.

Testing

  • tools/check-code-quality.sh passes: ESLint, Prettier, PHPStan, Pest and PHPCS. tsc and yarn build also pass.
  • In headless Chrome with jQuery 3.7.1, I fired each plugin's event or AJAX request on a page with 11 widgets:
    • each hook reset only the widget in its own form;
    • an unrelated ajaxComplete (heartbeat) reset nothing;
    • a reset event on document reset all of them.
  • With happy-dom I checked that both the public and legacy filled events fire, and that a reset event on one form doesn't touch the others.
  • Event names and payloads come from the plugin sources: CF7 6.2.4.1, WPForms Lite 1.9.9.2, Fluent Forms 6.2.16, Formidable 7.0, Ninja Forms 3.15.7, Everest Forms 3.6.3, JetFormBuilder 3.6.7.1, User Registration 5.3.1, WooCommerce 11.2 and Blocksy Companion 2.1.60. Elementor Pro and Beaver Builder Pro are paid plugins, so their AJAX actions come from hCaptcha's integration and our existing Beaver Builder prefilter.

Possible bugs found along the way (not fixed here)

  • User Registration AJAX login: ur-login.js posts a fixed set of fields that doesn't include procaptcha-response. With the "AJAX login" option on, logins with Procaptcha probably always fail.
  • Spectra: the token is read with getElementById('hidden').getAttribute('value'). We set .value, not the attribute, and the ID is shared across the whole page.

🤖 Generated with Claude Code

…d verifications

- A token can be verified only once, so forms that submit without a page
  reload now reset the widget after each response. Hooks the submission
  events of CF7, WPForms, Fluent Forms, Formidable, Ninja Forms,
  JetFormBuilder, User Registration and WooCommerce classic checkout; the
  AJAX action for Elementor Pro, Everest Forms and Beaver Builder; and the
  form class for the Blocksy newsletter.
- The widget listens for prosopo-procaptcha:reset on the document in the
  capture phase, and resets when the event target contains it, using the
  widget id returned by procaptcha.render().
- Public prosopo-procaptcha:filled / :cleared events. The legacy
  _prosopo-procaptcha__filled is still dispatched for existing code.
- Log failed verifications (status, reason, error) when WP_DEBUG is on, and
  add a prosopo/procaptcha/verification_result action with the response.
- Bump to 1.24.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@forgetso
forgetso merged commit c8ceb8d into main Oct 29, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant