Skip to content

Tutorial

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.

By 5 min readPublished

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. 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) 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:

{
"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 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 and Polling and 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:

Terminal window
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 page, and the other request format, createTask and getTaskResult, is explained in createTask and getTaskResult.

Sources

The team that builds and runs the ZeroCaptcha API. Articles are drafted with AI tools, then checked against the API's code and the primary sources each one cites.

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.

Read next

This article is part of the Cloudflare Turnstile solver hub. Every task is charged only when a token is ready.

Get an API key