A browser that runs JavaScript, fills in fields, and clicks a button might still be an AI agent. That's one of the cases we're working on with FCaptcha. I'm part of the WebDecoy team behind it.
FCaptcha is an MIT-licensed CAPTCHA that combines browser automation indicators, interaction and timing signals, and proof of work. It has checkbox and invisible modes and can run on your own server.
I put together a small Supabase Edge Function example showing how to use it before accepting a request.
The browser gets a token with action: 'contact' and sends it to the function with a sample message. The function verifies the token with FCaptcha using a server-only secret, then checks the signed hostname and action. Only after those checks does it reach the protected operation. The demo itself doesn't store anything or send email.
The core check, after validating the verifier response, is:
if (result.success !== true ||
result.hostname !== hostname ||
result.action !== 'contact') {
return reply(403, { error: 'captcha_rejected' });
}
There's a five-second timeout on the verification call. An unavailable verifier returns 503 rather than quietly accepting unchecked requests. Tokens are single-use, so the browser gets a fresh one for each attempt.
The repo includes the function, a browser demo, a local FCaptcha launcher, and setup instructions. The launcher creates separate temporary signing and verification secrets; neither goes into browser code. You can run the whole example locally with Node.js, Docker Desktop, and the Supabase CLI.
One detail that caught me during testing: the local Kong gateway answers preflight itself and can rewrite the CORS headers. The function still checks Origin on POST, but CORS is not authentication. The README calls out what I observed rather than assuming the handler's headers reach the browser unchanged.
Chris Portscheller shares an example of using FCaptcha in a Supabase Edge Function to detect bots and AI agents. The example demonstrates how to verify requests using a server-only secret and checks for signed hostname and action. Chris is interested in feedback on handling legitimate visitor failures during bot checks.
This is a custom Edge Function integration. It does not replace Supabase Auth's built-in hCaptcha/Turnstile setting or protect direct calls to your other Supabase APIs. The example function is deliberately public and has no database or service-role access. A route handling private user data still needs user authentication and authorization.
Seven Deno test groups pass, including signed fixtures against the real FCaptcha verifier for acceptance, replay, expiry, forged tokens, and wrong hostname/action. I also ran the function in Supabase's local Edge Runtime and checked its rejection responses. These are integration tests, not claims about detection accuracy or human pass rates. No hosted Supabase project was deployed.
The example is available on the linked branch while its PR is open. I'd be interested in how others handle recovery when a legitimate visitor fails a bot check, especially on an endpoint that needs to stay available before sign-in.