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
| Variable | What it does | Default |
|---|---|---|
CAPTCHA_PROVIDER | none, turnstile, hcaptcha or recaptcha, in lower case. | none |
CAPTCHA_SITE_KEY | The public key. It is sent to every visitor's browser. | none |
CAPTCHA_SECRET_KEY | The 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.
- In the Cloudflare dashboard, open Turnstile and add a widget.
- Give it your site's host name, such as
choir.example.org. - 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
- Create a site in your hCaptcha dashboard and add your host name.
- Copy the site key from the site's page, and your secret key from your account settings.
- Set
CAPTCHA_PROVIDER=hcaptchaand 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
| Where | Message |
|---|---|
| Contact form, box not ticked | Please complete the security check |
| Ticket or give-by-card form, box not ticked | Please complete the security check. |
| The provider refused the visitor, or could not be reached | Security 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:
| Provider | Script loaded from |
|---|---|
| Turnstile | https://challenges.cloudflare.com/turnstile/v0/api.js |
| hCaptcha | https://js.hcaptcha.com/1/api.js |
| reCAPTCHA | https://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 see | Cause and fix |
|---|---|
CAPTCHA_PROVIDER=turnstile needs CAPTCHA_SITE_KEY and CAPTCHA_SECRET_KEY from npm run config:check and in the start-up log | One or both keys are not set. See Check your configuration. |
| The box never appears, and the contact form's button stays greyed out | The 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_PROVIDER | The 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 similar | The secret key is wrong, or belongs to another site key. |
| The box shows an error about the domain | The provider's key does not list your host name. Add it in the provider's dashboard. |