Skip to main content

Stop spam with a bot check

Public forms attract spam. A bot check is a small box that a real visitor passes and a script does not. It is off by default. This page is for whoever runs the server and has seen junk arriving through the contact form, or wants the ticket and donation forms protected before they are busy.

What it protects​

Three forms on the public site:

  • the contact form (the Send Message button);
  • the ticket order form, where someone buys or reserves tickets;
  • the give-by-card form on the Donate page.

Nothing else is checked: not logins, not unsubscribe links. When a provider is chosen, the box appears in each of those forms and the visitor cannot send until it is ticked. On the contact form the Send Message button stays greyed out until then.

Settings​

VariableWhat it doesDefault
CAPTCHA_PROVIDERnone, turnstile, hcaptcha or recaptcha, in lower case.none
CAPTCHA_SITE_KEYThe public key. It is sent to every visitor's browser.none
CAPTCHA_SECRET_KEYThe secret key. The server uses it to ask the provider whether a visitor passed. Never shown to browsers.none

Restart the server after changing them. On Cloudflare, put CAPTCHA_PROVIDER and CAPTCHA_SITE_KEY under [vars] in wrangler.toml, set the secret key with npx wrangler secret put CAPTCHA_SECRET_KEY, and deploy again.

CAPTCHA_PROVIDER=turnstile
CAPTCHA_SITE_KEY=0x4AAAAAAA-example-site-key
CAPTCHA_SECRET_KEY=0x4AAAAAAA-example-secret-key

Cloudflare Turnstile​

Cloudflare's own check. It does not need a Cloudflare-hosted site.

  1. In the Cloudflare dashboard, open Turnstile and add a widget.
  2. Give it your site's host name, such as choir.example.org.
  3. Copy the site key and secret key it shows into the settings above, with CAPTCHA_PROVIDER=turnstile.

Cloudflare publishes keys that always pass, for trying it out: site key 1x00000000000000000000AA and secret key 1x0000000000000000000000000000000AA. Never leave them on a live site.

hCaptcha​

  1. Create a site in your hCaptcha dashboard and add your host name.
  2. Copy the site key from the site's page, and your secret key from your account settings.
  3. Set CAPTCHA_PROVIDER=hcaptcha and the two keys.

Google reCAPTCHA​

Create the keys as reCAPTCHA v2, with the "I'm not a robot" checkbox type, at google.com/recaptcha/admin, and add your host name. The widget the site shows is the v2 checkbox. A v3 or invisible key does not work with the form, because there is no box for the visitor to tick. Set CAPTCHA_PROVIDER=recaptcha and the two keys.

What visitors see when it fails​

WhereMessage
Contact form, box not tickedPlease complete the security check
Ticket or give-by-card form, box not tickedPlease complete the security check.
The provider refused the visitor, or could not be reachedSecurity verification failed. Please try again. The box then asks for a fresh check.

A real visitor can fail when their token has expired because they were slow, or when the server cannot reach the provider. The server logs the provider's reason as turnstile verification failed: (or hcaptcha, recaptcha) followed by the provider's error codes. The most common code is a secret key that does not belong to the site key.

Content-Security-Policy and firewalls​

The site itself sets no Content-Security-Policy for its pages. If your reverse proxy adds one, it must allow the widget, which loads its script from the provider:

ProviderScript loaded from
Turnstilehttps://challenges.cloudflare.com/turnstile/v0/api.js
hCaptchahttps://js.hcaptcha.com/1/api.js
reCAPTCHAhttps://www.google.com/recaptcha/api.js

The widgets then load frames and more files from the provider's own domains (and, for reCAPTCHA, from www.gstatic.com). If the box does not appear and the browser's console talks of blocked content, add the provider's domains to script-src, frame-src and connect-src. Which exactly they need is the provider's to say; their documentation has the current list.

The server also has to reach the provider to check the answer: challenges.cloudflare.com, api.hcaptcha.com or www.google.com, over HTTPS. A server that cannot make outbound requests fails every check.

Turn it off​

Set CAPTCHA_PROVIDER=none, or remove the line. Do not leave it empty (CAPTCHA_PROVIDER=): an empty value is not treated as none, and the forms then ask for a box that never appears. The keys can stay where they are.

If something goes wrong​

What you seeCause and fix
CAPTCHA_PROVIDER=turnstile needs CAPTCHA_SITE_KEY and CAPTCHA_SECRET_KEY from npm run config:check and in the start-up logOne or both keys are not set. See Check your configuration.
The box never appears, and the contact form's button stays greyed outThe script is blocked (a Content-Security-Policy, an ad blocker, a firewall) or the provider name is misspelt. Names are lower case.
Every form fails with Security verification failed, and the log says Unknown CAPTCHA_PROVIDERThe name is not one of the four. The configuration check does not report this when both keys are set.
Every form fails and the log has invalid-input-secret or similarThe secret key is wrong, or belongs to another site key.
The box shows an error about the domainThe provider's key does not list your host name. Add it in the provider's dashboard.