Post to the form endpoint without a browser
Send a request with no challenge token and read the status code. A success status means the handler does not verify anything server-side, so the fix belongs in the handler.
curl -si -d '[email protected]&message=probe' https://YOUR-SITE/api/contact | head -1
Docs: developers.cloudflare.com →
Verify the challenge token on the server before sending mail
Add a challenge widget to the form, then have the handler send the token and your secret key to Siteverify. Send the email only if the result says success. The docs state that you must call Siteverify to complete the implementation.
const token = body.get("cf-turnstile-response");
const res = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ secret: SECRET_KEY, response: token }),
});
const result = await res.json();
if (!result.success) return new Response("Rejected", { status: 400 });
Docs: developers.cloudflare.com →
Handle expired and replayed tokens
A token is valid for 300 seconds and can be validated once, so a visitor who leaves the form open or resubmits will be rejected with timeout-or-duplicate. Show a message asking them to complete the challenge again rather than treating it as a bot.
Docs: developers.cloudflare.com →
Rate limit the form endpoint
Count submissions per client, typically by IP, and return 429 Too Many Requests once a client passes your threshold. Add a Retry-After header to tell the client how long to wait.
return new Response("Too Many Requests", { status: 429, headers: { "Retry-After": "3600" } });
Docs: developer.mozilla.org →
Require approval for bookings on public links
In Cal.com, open the link, go to Policies, then Privacy & security, toggle on Requires confirmation and save. Pending bookings send no confirmation email until you approve them, and they wait under the Unconfirmed tab.
Docs: cal.com →
Decide whether unconfirmed bookings should hold your calendar
The setting Unconfirmed bookings still block calendar slots holds the time for a pending request. If bots are filling the calendar, turn it off so pending requests do not occupy slots. Then clear the existing backlog from the Unconfirmed tab.
Docs: cal.com →