Turnstile Verify Endpoint always returns 'Bad Request'

I was just about to integrate Turnstile into my website. Unfortunately, with Python requests for server-side verification, I always get a ‘bad request’ error. I have tried with all possible variations of Secret and Response. But for the sake of simplicity, I created this post without any parameters. The error message here should be ‘timeout-or-duplicate’ though. I tested it on RestMan for Chrome, there the post request worked fine.

import requests
result = requests.post('https://challenges.cloudflare.com/turnstile/v0/siteverify')
print(result.text)

Response Python:

{"success":false,"error-codes":["bad-request"]}

Response RestMan (Same URL):

{"success": false,"error-codes": ["timeout-or-duplicate"]}

Hi @sascha.zesiger, could you provide an example of your Python code, including how you’re defining the POST parameters? You can replace the parameter values with other example strings.

My Python Code with Parameters:

import requests
result = requests.post('https://challenges.cloudflare.com/turnstile/v0/siteverify?secret=000000&response=000000')
print(result.text)

This Code is working fine for Recaptcha but always ends in a ‘Bad-Request’ with Turnstile.

I’d assume Turnstile is expecting a form body rather than query parameters - i.e application/x-www-form-urlencoded or multipart/form-data.

The examples from the documentation both use forms.

curl 'https://challenges.cloudflare.com/turnstile/v0/siteverify' --data 'secret=verysecret&response=<RESPONSE>' results in a application/x-www-form-urlencoded body being sent.

=> Send header, 180 bytes (0xb4)
0000: 50 4f 53 54 20 2f 74 75 72 6e 73 74 69 6c 65 2f POST /turnstile/
0010: 76 30 2f 73 69 74 65 76 65 72 69 66 79 20 48 54 v0/siteverify HT
0020: 54 50 2f 32 0d 0a 48 6f 73 74 3a 20 63 68 61 6c TP/2..Host: chal
0030: 6c 65 6e 67 65 73 2e 63 6c 6f 75 64 66 6c 61 72 lenges.cloudflar
0040: 65 2e 63 6f 6d 0d 0a 75 73 65 72 2d 61 67 65 6e e.com..user-agen
0050: 74 3a 20 63 75 72 6c 2f 37 2e 38 34 2e 30 0d 0a t: curl/7.84.0..
0060: 61 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 63 6f 6e accept: */*..con
0070: 74 65 6e 74 2d 6c 65 6e 67 74 68 3a 20 33 37 0d tent-length: 37.
0080: 0a 63 6f 6e 74 65 6e 74 2d 74 79 70 65 3a 20 61 .content-type: a
0090: 70 70 6c 69 63 61 74 69 6f 6e 2f 78 2d 77 77 77 pplication/x-www
00a0: 2d 66 6f 72 6d 2d 75 72 6c 65 6e 63 6f 64 65 64 -form-urlencoded
00b0: 0d 0a 0d 0a                                     ....
=> Send data, 37 bytes (0x25)
0000: 73 65 63 72 65 74 3d 76 65 72 79 73 65 63 72 65 secret=verysecre
0010: 74 26 72 65 73 70 6f 6e 73 65 3d 3c 52 45 53 50 t&response=<RESP
0020: 4f 4e 53 45 3e                                  ONSE>

The fetch example uses a FormData object.

	// Validate the token by calling the
	// "/siteverify" API endpoint.
	let formData = new FormData();
	formData.append('secret', SECRET_KEY);
	formData.append('response', token);
	formData.append('remoteip', ip);

	const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
	const result = await fetch(url, {
		body: formData,
		method: 'POST',
	});

Looks like ReCAPTCHA is fine with query parameters but Turnstile isn’t.

> GET /recaptcha/api/siteverify?secret=verysecret&response=<RESPONSE> HTTP/2
> Host: www.google.com
> user-agent: curl/7.84.0
> accept: */*
>
< HTTP/2 200
< snip
<
{
  "success": false,
  "error-codes": [
    "invalid-input-secret"
  ]
* Connection #0 to host www.google.com left intact
}%
> GET /turnstile/v0/siteverify?secret=verysecret&response=<RESPONSE> HTTP/2
> Host: challenges.cloudflare.com
> user-agent: curl/7.84.0
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 256)!
< HTTP/2 404
< snip
<
* Connection #0 to host challenges.cloudflare.com left intact
Invalid%

Thanks for the Answer. The Problem is more that I get a ‘Bad-Request’ Error with no Parameters at all in Python… The Error Message for a Query with no Parameters should be ‘invalid-input-secret’