captchaapi/laravel
Official Laravel SDK for captchaapi.eu, an EU-hosted, GDPR-compliant proof-of-work CAPTCHA. Ships a drop-in Blade component, validation rule, and Livewire trait. No cookies, no tracking, no Google—server-side verification keeps your secret key safe.
Architecture fit. The package is a thin, well-scoped adapter rather than a framework: a Blade widget component, a ValidCaptcha validation rule (plus a captcha string alias), a Livewire WithCaptcha trait, and a livewire-form wrapper. That is the right shape for a Laravel integration — it slots into the existing validation pipeline and Form Request / $request->validate() patterns instead of introducing a parallel verification mechanism, and it does not fight the framework's conventions. The Livewire support is the genuine differentiator: first-class trait + event-mode wrapper is meaningfully less integration work than hand-rolling a CustomEvent bridge, and Livewire-heavy stacks are common in the target market. The trade-off is that the package is only the client half — the actual verdict comes from captchaapi.eu, a commercial SaaS endpoint. We are evaluating a dependency on a vendor service as much as on a Composer package.
Integration feasibility. Low engineering effort. A developer can go from composer require to a guarded form in well under a day following the README: publish config, set two env vars, drop <x-captchaapi::widget /> in the layout, add data-captcha to the form, add the rule. The inclusion of FakeCaptchaapi::enable() for feature tests, a permanent CAPTCHAAPI_ENABLED kill switch, and documented Octane safety (per-request memo on the request, not the singleton; fake state throws outside testing) signals the maintainers understand production Laravel. Secret rotation with an overlap window is a mature operational detail that most hobby-tier CAPTCHA packages lack.
Technical risk. This is where the assessment turns cautious. Three concerns dominate:
Key questions.
Stack fit. Good-to-excellent for a specific profile: Laravel 12/13, PHP 8.2+, Blade-rendered forms, Livewire 4 components, optionally Octane. It also fits well with Fortify-based auth — the README explicitly documents Fortify's double-validation behavior and provides enforceSingleUse() to prove the integration handles it, which is exactly the kind of edge case that silently breaks naive CAPTCHA integrations. It fits poorly for API-only services, Inertia/Vue front ends without Blade (the widget is a Blade component; an SPA would need manual wiring of mode=event), and any application pinned below Laravel 12.
Migration path. Treat this as a parallel-run replacement, not a rip-and-replace, for reCAPTCHA/hCaptcha. Because CAPTCHAAPI_ENABLED=false makes the rule pass silently and the widget render nothing, the same code can ship to every environment and be enabled per-environment or per-route. Recommended path:
CAPTCHAAPI_ENABLED=false everywhere. Zero behavioral change, all plumbing validated by the existing test suite.Compatibility. Verify before committing: exact Laravel minor version support (the "12 or 13" claim should be confirmed against our current lockfile), Livewire 4 availability if we depend on the trait, CSP headers (the widget script loads from captchaapi.eu, and the inline config push to window.CAPTCHA_* may need a nonce or hash), and EU egress from all application subnets including queue workers and any Octane workers. If we run Fortify, budget an explicit test using enforceSingleUse() so the double-validation path is proven rather than assumed.
Sequencing. Package evaluation and CSP/egress work first (cheap, no production exposure). Then fake-mode test coverage of the validation rule across all protected routes — this is the highest-leverage step because it validates the integration without any vendor dependency. Then a single production form. Then breadth. The fail-open policy decision should be made per route class, not globally, before any enablement — see below.
Maintenance. Small surface area: one Composer dependency, two env vars, one config file, and a handful of Blade components. The real maintenance burden is not the package but the version window — supporting only Laravel 12/13 means our Laravel upgrade cadence is coupled to the maintainers' release cadence, and a lag on their side could block a security-driven framework upgrade on ours. Budget a standing line item for reviewing their changelog each Laravel major. The narrow adoption (0 dependents) also means we are effectively the QA function: we will find integration bugs before anyone else does.
Support. Single-vendor, GitHub-issue-based support with no published SLA and no commercial support tier mentioned. Security disclosures route through GitHub private advisories, which is correct practice but assumes an attentive maintainer. Realistically this is self-supported: we should be prepared to read the source. Mitigating factor — the package is MIT and small, so forking and patching is a viable escape hatch if the maintainer goes quiet.
Scaling. Every protected submission adds one synchronous outbound HTTP call with a 5-second default timeout. That is the key scaling characteristic: it is a serial dependency in the request path. At low volume it is invisible; at high volume on a login or checkout route it becomes a tail-latency contributor and, under vendor degradation, a worker-pool exhaustion risk (5s × concurrent submissions). Mitigations: lower CAPTCHAAPI_VERIFY_TIMEOUT to 2–3s, cap concurrency on protected routes, and consider whether protected routes should run on a separate worker pool. Octane compatibility is explicitly documented and correctly implemented, so there is no cross-request state-leak concern — that is a genuine plus and removes a whole class of failure mode.
Failure modes. The package's own documentation is unusually candid here, which is worth crediting:
fail_open, default true (let the submission through). Correct default for a marketing contact form; wrong for login and payment. This must be set per route class (CAPTCHAAPI_FAIL_OPEN=false for auth and payment) rather than accepted as a global default — a single global true quietly means "our CAPTCHA turns itself off during any vendor outage," and an attacker who can induce or time an outage gets a free pass.ValidCaptcha rejects invalid, expired, and already-used tokens with a field-scoped error. Rejections surface as a normal Laravel validation error, so user-facing behavior degrades gracefully.rate_limited state; confirm our own route throttles are still in place rather than treating the CAPTCHA as a rate limiter.enabled=true with a null site_key is a plausible silent-failure state; add a startup or health check asserting that if enabled, both keys are present.Ramp-up. Low for a Laravel developer — the README is genuinely good, with copy-pasteable examples for plain forms, Livewire, error rendering, status styling, testing, and Octane. Estimate half a day for a first integration, one to two days for full test coverage across protected routes plus the fail-policy matrix. Two adoption frictions to plan for: the Tailwind/design-system path requires deliberately opting out via data-captcha-no-color and then supplying per-state colors (the waiting/ready icon collision will otherwise produce a confusing UI), and the opt-in [data-captcha-status] element means a team that skips it ships a silently-working form with no user-visible progress signal — a support-ticket generator during vendor slowness.
Overall TPM recommendation. Technically well-built and unusually honest about its own trade-offs; a strong fit for EU-facing Laravel 12/13 + Livewire shops that need a GDPR-clean, accessible, frictionless CAPTCHA on low-to-medium-risk public forms. The blockers are not code quality — they are vendor concentration, a narrow framework support window, and a proof-of-work threat model that should not be relied on for auth or payment without fail-closed configuration and complementary controls. Recommend a scoped pilot on non-sensitive forms with an explicit per-route fail-policy decision and a documented fork-if-abandoned contingency, rather than a broad rollout.
How can I help you explore Laravel packages today?