# Turnstile Verify (/docs/experiments/turnstile-verify)



Verify [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/) tokens server-side by calling the siteverify API from a Worker. This experiment uses the `TURNSTILE_SECRET_KEY` environment secret - there is no Turnstile Worker binding.

## API Reference [#api-reference]

### POST /verify [#post-verify]

Validates a client Turnstile token against the Cloudflare siteverify endpoint and returns the verification result.

<TypeTable
  type="{
  token: {
    description: &#x22;Turnstile response token from the client widget&#x22;,
    type: &#x22;string&#x22;,
    required: true,
  },
}"
/>

#### Example Request [#example-request]

```bash
curl -X POST "https://your-worker.workers.dev/verify" \
  -H "Content-Type: application/json" \
  -d '{"token":"0.abc123..."}'
```

#### Success Response [#success-response]

**`success`** `boolean`

Whether Turnstile accepted the token

**`hostname`** `string` (optional)

Hostname associated with the token, when provided by siteverify

**`action`** `string` (optional)

Custom action name from the widget, when provided by siteverify

**`errorCodes`** `string[]` (optional)

Present when `success` is `false`; Turnstile error codes from siteverify

```json
{
  "success": true,
  "hostname": "example.com",
  "action": "login"
}
```

When verification fails:

```json
{
  "success": false,
  "errorCodes": ["invalid-input-response"]
}
```

#### Error Response [#error-response]

```json
{
  "error": "Missing or invalid field: token",
  "code": "INVALID_TOKEN"
}
```

#### Error Codes [#error-codes]

* `400` - Invalid JSON body (`INVALID_BODY`)
* `400` - Missing or invalid `token` (`INVALID_TOKEN`)
* `502` - Turnstile secret key is not configured (`MISSING_SECRET`)
* `502` - Siteverify request failed (`VERIFY_ERROR`)

## Use Cases [#use-cases]

* Validate Turnstile tokens on form submission before processing requests
* Protect login, signup, or contact endpoints from bots at the edge
* Learn server-side Turnstile verification without a backend framework
* Return structured siteverify results to your frontend or API gateway

## Limitations [#limitations]

* Requires a `TURNSTILE_SECRET_KEY` secret configured in the Worker
* Server-side verification only; does not render the Turnstile widget
* Turnstile tokens are single-use and expire quickly

## Deployment [#deployment]

<Steps>
  <Step>
    ### Click the deploy button [#click-the-deploy-button]

    [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/shrinathsnayak/cloudflare-experiments/tree/main/apps/experiments/turnstile-verify)
  </Step>

  <Step>
    ### Set the Turnstile secret [#set-the-turnstile-secret]

    After deploy, set your Turnstile **secret key** as a Worker secret:

    ```bash
    cd apps/experiments/turnstile-verify
    npx wrangler secret put TURNSTILE_SECRET_KEY
    ```

    Use the secret key from your Turnstile widget configuration in the Cloudflare dashboard.
  </Step>

  <Step>
    ### Test your deployment [#test-your-deployment]

    ```bash
    curl -X POST "https://your-worker.workers.dev/verify" \
      -H "Content-Type: application/json" \
      -d '{"token":"YOUR_TURNSTILE_TOKEN"}'
    ```
  </Step>
</Steps>

## Local Development [#local-development]

```bash
cd apps/experiments/turnstile-verify
npm install
npx wrangler secret put TURNSTILE_SECRET_KEY
npm run dev
```

Test locally:

```bash
curl -X POST "http://localhost:8787/verify" \
  -H "Content-Type: application/json" \
  -d '{"token":"YOUR_TURNSTILE_TOKEN"}'
```

## Cloudflare Features Used [#cloudflare-features-used]

* **[Workers](https://developers.cloudflare.com/workers/)** - Edge compute runtime
* **[Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/)** - siteverify API for token validation
