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 ZeroCaptcha Engineering5 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, URLhttps://<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 ofqueuedorrunning(use a regex match on^(queued|running)$), and{{ $runIndex }}is less than90. 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:
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
- n8n: HTTP Request node and HTTP Request credentials (checked 1 October 2026).
- n8n: HTML node (checked 1 October 2026).
- n8n: Wait node (checked 1 October 2026).
- n8n: loop and
n8n metadata, for
$runIndexand$execution.resumeUrl(checked 1 October 2026). - Cloudflare Turnstile: client-side rendering (checked 1 October 2026).
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.