# n8n and Cloudflare Turnstile: Solve a Form in a Workflow

> Build an n8n workflow that reads a Cloudflare Turnstile sitekey, gets a token from a solving API with HTTP Request nodes, waits in a loop, and submits the form.

- Source: https://zerocaptcha.io/blog/n8n-cloudflare-turnstile
- Published: 2026-10-01
- Author: ZeroCaptcha Engineering

n8n cannot run a Cloudflare Turnstile widget, but it does not need to: its **HTTP Request** node
can call a solving API. The workflow is five steps: **read the sitekey** from the page with the
HTML node, **create a task** with the page URL and sitekey, **wait** 2 seconds, **read the task**,
and **loop** back to the wait with an If node until the status is final. Then send the token in the
form's `cf-turnstile-response` field with one more HTTP Request node. This tutorial builds it with
ZeroCaptcha's REST API and a Bearer credential, so the key stays out of the workflow's JSON.

Automate only forms you are allowed to: see
[responsible captcha automation](https://zerocaptcha.io/guides/responsible-captcha-automation). Every task is real and
charged when it succeeds, so test the workflow on a page you control first.

## The workflow

| # | Node | What it does |
| --- | --- | --- |
| 1 | Manual Trigger or Schedule Trigger | Starts the run |
| 2 | HTTP Request "Load page" | `GET` the page with the form, Response Format Text, into the field `data` |
| 3 | HTML "Read sitekey" | Extract HTML content: CSS selector `[data-sitekey]`, return value Attribute `data-sitekey` |
| 4 | HTTP Request "Create task" | `POST /v1/tasks` with the page URL and sitekey |
| 5 | Wait | After Time Interval, 2 seconds |
| 6 | HTTP Request "Read task" | `GET /v1/tasks/{id}` |
| 7 | If "Still working?" | Status is `queued` or `running`, and fewer than 90 reads: back to 5 |
| 8 | If "Solved?" | Status is `succeeded` |
| 9 | HTTP Request "Submit form" | `POST` the form with the token |

## The credential

Create a credential of type **Bearer Auth** (one of the HTTP Request node's generic credential
types) with your ZeroCaptcha API key as the token. n8n's documentation describes it as "just header
authentication with the `Name` set to `Authorization` and the `Value` set to `Bearer <token>`",
which is what ZeroCaptcha's REST API expects. Use it in nodes 4 and 6.

## Read the sitekey

Node 2 fetches the page with the option **Response Format** set to **Text** and **Put Output in
Field** set to `data`. Node 3, the HTML node, uses **Extract HTML content** with source data JSON
and the JSON property `data`, and one extraction value: the Key `sitekey`, a CSS selector of
`[data-sitekey]`, and **Return Value** set to **Attribute**, naming `data-sitekey`. Add two more
extractions with the same selector: the Key `action` for the attribute `data-action`, and the Key
`cdata` for `data-cdata`. Many sites check both when they verify the token. If the widget is
rendered by JavaScript and the attributes are not in the HTML, find the sitekey, and the `action`
and `cData` options of its `turnstile.render()` call, once by hand
([find a Cloudflare Turnstile sitekey](https://zerocaptcha.io/guides/find-cloudflare-turnstile-sitekey)) and type them in.

## Create the task

Node 4: method `POST`, URL `https://<your ZeroCaptcha API address>/v1/tasks`, authentication with the
Bearer credential, **Send Body** on, content type **JSON**, and this body with the expressions
switched on:

```json
{
  "type": "TurnstileTaskProxyless",
  "websiteURL": "https://shop.example.com/contact",
  "websiteKey": "{{ $json.sitekey }}",
  "action": "{{ $json.action }}",
  "cdata": "{{ $json.cdata }}"
}
```

Remove `action` or `cdata` from the body when the widget does not set it, rather than sending it
empty. To be called when the task ends instead of polling, add a `callbackUrl` (see below). The reply is the task,
with its `id` and `status` `queued`. Turn on **Send Headers** and add `Idempotency-Key` with the
value `{{ $execution.id }}-{{ $json.sitekey }}`, so that a retry of this node in the same run gets
the first task back instead of creating a second one.

## Wait, read, loop

- **Node 5, Wait:** resume **After Time Interval**, Wait Amount `2`, Wait Unit **Seconds**. n8n's
  docs say: "For wait times less than 65 seconds, the workflow doesn't offload execution data to
  the database." The execution keeps running, which is what a short poll wants.
- **Node 6, Read task:** method `GET`, URL
  `https://<your ZeroCaptcha API address>/v1/tasks/{{ $json.id }}`, the same Bearer credential.
- **Node 7, If "Still working?":** two conditions joined by AND: `{{ $json.status }}` is one of
  `queued` or `running` (use a regex match on `^(queued|running)$`), and `{{ $runIndex }}` is less
  than `90`. Connect the **true** output back to node 5.

That is n8n's documented pattern: "To create a loop in an n8n workflow, connect the output of one
node to the input of a previous node. Add an IF node to check when to stop the loop." `$runIndex` is
"How many times n8n has executed the current node", so 90 reads, 2 seconds apart, cap the wait at
three minutes, past the task's own 150-second deadline.

Node 8 then checks that `{{ $json.status }}` equals `succeeded`. On its false branch, the task
`failed` or `expired`, and nothing was charged: log `{{ $json.errorCode }}` and stop, or create a new
task.

## Submit the form

Node 9: method `POST`, URL of the form's `action`, **Send Body** on, content type
**Form URLencoded**, with the form's fields and the token:

| Name | Value |
| --- | --- |
| `email` | `you@example.com` |
| `message` | `Sent from n8n` |
| `cf-turnstile-response` | `{{ $json.solution.token }}` |

The token is valid for 300 seconds and works once, so keep node 9 right after the loop. If the site
sends the token in a JSON body or under another name, copy what its page sends: submit the form by
hand with the browser's network panel open, and mirror that request.
[Submit a Cloudflare Turnstile token](https://zerocaptcha.io/guides/submit-cloudflare-turnstile-token) covers the variants.

## Callbacks instead of polling

ZeroCaptcha can also call you when a task ends: name a `callbackUrl` when you create it. n8n's Wait
node has a matching mode, **On Webhook Call**, and exposes the URL to resume at as
`$execution.resumeUrl`, so the create body can carry `"callbackUrl": "{{ $execution.resumeUrl }}"`
with the Wait node set to resume on a `POST`. Two conditions apply: the n8n instance must be reachable
from the internet at a public domain, and each call is signed with an HMAC-SHA256 signature that you
should check before trusting it, which needs the raw request body in a Code node. If either is a
problem, poll as above; polling always works. See
[captcha solver callbacks](https://zerocaptcha.io/guides/captcha-solver-callbacks) and
[Polling and callbacks](https://zerocaptcha.io/docs/callbacks).

## The same calls in curl

When a node misbehaves, run its request from a terminal to compare. These are the calls nodes 4 and
6 make:

```sh
curl -sS --fail-with-body "$ZEROCAPTCHA_API/v1/tasks" \
  -H "Authorization: Bearer $ZEROCAPTCHA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: test-run-0001" \
  -d '{"type": "TurnstileTaskProxyless", "websiteURL": "https://shop.example.com/contact",
       "websiteKey": "0x4AAAAAAAB1cD2eF3gH4iJ5", "action": "contact", "cdata": "session-7f3a9c2e"}'

curl "$ZEROCAPTCHA_API/v1/tasks/$TASK_ID" -H "Authorization: Bearer $ZEROCAPTCHA_KEY"
```

More curl examples are on the [Cloudflare Turnstile solver for curl](https://zerocaptcha.io/cloudflare-turnstile-solver/curl)
page, and the other request format, `createTask` and `getTaskResult`, is explained in
[createTask and getTaskResult](https://zerocaptcha.io/guides/createtask-gettaskresult-explained).

## Sources

- [n8n: HTTP Request node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest)
  and [HTTP Request credentials](https://docs.n8n.io/integrations/builtin/credentials/httprequest)
  (checked 1 October 2026).
- [n8n: HTML node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.html)
  (checked 1 October 2026).
- [n8n: Wait node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait)
  (checked 1 October 2026).
- [n8n: loop](https://docs.n8n.io/build/flow-logic/loop) and
  [n8n metadata](https://docs.n8n.io/build/code-in-n8n/use-built-in-shortcuts/n8n-metadata), for
  `$runIndex` and `$execution.resumeUrl` (checked 1 October 2026).
- [Cloudflare Turnstile: client-side rendering](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/)
  (checked 1 October 2026).

## Questions

### Can n8n solve Cloudflare Turnstile?

n8n cannot run the widget itself, but its HTTP Request node can call a solving API: create a task with the page URL and sitekey, wait, read the task until it has a token, and send the token in the form's cf-turnstile-response field.

### How do I loop in n8n until a task is ready?

Connect the output of a later node back to an earlier one, and add an If node that decides when to stop, as n8n's loop documentation describes. Here: Wait, then read the task, then an If node that goes back to Wait while the status is queued or running.

### How long should the n8n Wait node wait between polls?

Two seconds, the interval the API asks for. For waits of less than 65 seconds, n8n keeps the execution running instead of offloading it to the database, which suits a short poll.
