How to Solve Cloudflare Challenge in n8n with CapSolver

Ethan Collins
Pattern Recognition Specialist
12-Mar-2026

Cloudflare's bot protection goes far beyond CAPTCHA widgets. The Cloudflare Challenge โ the full-page "Verifying your browserโฆ" screen that blocks access to a site entirely โ is one of the most aggressive bot defenses on the web. It runs a full JavaScript challenge in a real browser environment, checks behavioral signals, and fingerprints your TLS connection before it ever lets you through.
Standard automation tools fail here. Not because they can't solve the challenge, but because Cloudflare fingerprints the TLS handshake itself โ and any non-browser HTTP client gets blocked before the challenge is even served, or re-challenged immediately after, even with a valid cf_clearance cookie.
In this guide, you'll learn how to build a Cloudflare Challenge scraper in n8n that actually works โ combining CapSolver to solve the challenge, a local Go TLS server to spoof Chrome's TLS fingerprint, and an n8n workflow to tie it all together.
What you'll build:
- A dual-trigger workflow (schedule + webhook) that bypasses Cloudflare Bot Management
- A lightweight Go server that makes HTTP requests with a real Chrome TLS fingerprint
- A reusable scraper template you can point at any CF-protected site
- Use-case workflows for site health monitoring, price monitoring, account login, and account signup โ all behind Cloudflare Challenge
What Is the Cloudflare Challenge?
The Cloudflare Challenge (also called the JS challenge or Bot Management challenge) is a full-page interstitial that Cloudflare injects before a visitor can access a protected site. You've seen it: a black or white screen with "Verifying your browserโฆ" or "Just a momentโฆ" and a loading bar or Cloudflare logo.
Unlike Turnstile โ which is a small widget embedded inside a page โ the Cloudflare Challenge takes over the entire page. You cannot access any content until it completes.
| Cloudflare Challenge | Cloudflare Turnstile | |
|---|---|---|
| Where it appears | Full-page interstitial โ blocks site access entirely | Embedded widget inside a page (e.g., login form) |
| What it looks like | "Verifying your browserโฆ" loading screen | A small checkbox or invisible widget in a form |
| Who adds it | Cloudflare adds it automatically based on security rules | The website owner embeds it in their HTML |
| Solving approach | AntiCloudflareTask โ requires a proxy |
AntiTurnstileTaskProxyLess โ no proxy needed |
| Cookie returned | cf_clearance (domain-specific, IP-bound) |
Turnstile token (short-lived, used once) |
| Proxy required? | Yes โ same IP must be used for solve and fetch | No |
If you see an embedded checkbox or widget inside a form, that's Turnstile โ not this challenge. Check the CapSolver guide on identifying challenge types if you're unsure.
Why Standard HTTP Clients Fail
Here's the problem that most guides skip over: Cloudflare checks your TLS fingerprint, not just your cookies.
When a browser connects to a website over HTTPS, it sends a TLS ClientHello that includes details about its supported cipher suites, extensions, and settings. Cloudflare records this fingerprint (called a JA3 or JA4 fingerprint) and compares it to known browser profiles.
Go's net/http, Python's requests, curl, and most HTTP libraries all have distinct TLS fingerprints. Even if CapSolver successfully solves the challenge and returns a valid cf_clearance cookie, Cloudflare will re-challenge or block the request if it detects a non-browser TLS fingerprint on the subsequent fetch.
The fix: a Go server using httpcloak โ a library that spoofs a real Chrome TLS stack including:
- JA3 / JA4 fingerprint
- HTTP/2 SETTINGS frames
- ALPN negotiation
- Header ordering and values
This makes the fetch look exactly like a Chrome browser request at the network level.
Architecture Overview
This solution has three components:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ n8n Workflow โ
โ โ
โ Trigger โ Set Config โ CapSolver (AntiCloudflareTask) โ
โ โ Code Node โ HTTP Request โโโโโโโโโโโโโโโโโโโบ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ POST /fetch
โโโโโโโโโโโโผโโโโโโโโโโโ
โ TLS Server :7878 โ
โ (Go + httpcloak โ
โ Chrome preset) โ
โโโโโโโโโโโโฌโโโโโโโโโโโ
โ HTTPS (Chrome TLS)
โโโโโโโโโโโโผโโโโโโโโโโโ
โ CF-Protected Site โ
โโโโโโโโโโโโโโโโโโโโโโโ
| Component | Role |
|---|---|
| n8n workflow | Orchestrates the solve โ fetch โ extract pipeline |
| CapSolver | Runs AntiCloudflareTask through a proxy, returns cf_clearance + userAgent |
| TLS Server | Go binary using httpcloak's latest Chrome preset to make the actual HTTPS fetch |
Prerequisites
| Requirement | Notes |
|---|---|
| n8n self-hosted | Required โ the TLS server must run on the same machine as n8n. n8n Cloud is not suitable for this use case. |
| CapSolver account | Sign up here and get your API key |
| Go 1.21+ | Must be installed on the n8n server. Check with go version. |
| Residential or mobile proxy | Datacenter proxies will fail on most Cloudflare-protected sites. See Proxy Requirements below. |
Proxy Requirements
AntiCloudflareTask requires a proxy because CapSolver needs to load the actual protected page through a browser to solve the challenge. The proxy IP is bound to the cf_clearance cookie โ you must use the exact same proxy for the subsequent page fetch.
The workflow uses a single proxy field in host:port:user:pass format. This is the format CapSolver expects. The Prepare TLS Request code node automatically converts it to http://user:pass@host:port URL format for the TLS server using a toProxyURL() helper โ you don't need to manage two separate proxy fields.
Datacenter proxies (IPs from cloud providers, VPS hosts) are flagged aggressively by Cloudflare's bot scoring system. For most Cloudflare-protected sites, you need a residential or mobile proxy with sticky/session support so the IP stays the same across the solve and fetch.
Setting Up CapSolver in n8n
CapSolver is available as an official integration in n8n โ no community node installation required.
Step 1: Open the Credentials Page
Go to your n8n instance and navigate to Settings โ Credentials.

Step 2: Create the CapSolver Credential
- Click Create credential (top right)
- Search for "CapSolver" and select CapSolver API
- Enter your API Key from the CapSolver Dashboard
- Leave Allowed HTTP Request Domains set to
All - Click Save
You should see a green "Connection tested successfully" banner.

Step 1 โ Build the TLS Server
This Go server receives fetch requests from n8n and executes them using httpcloak's Chrome TLS profile. It's a small, self-contained binary that you run alongside n8n.
Create the source file
Create a directory and save the following as main.go:
bash
mkdir -p ~/tls-server && cd ~/tls-server
go
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
"time"
"github.com/sardanioss/httpcloak/client"
)
type FetchRequest struct {
URL string `json:"url"`
Method string `json:"method"`
Headers map[string]string `json:"headers"`
Proxy string `json:"proxy"`
Body string `json:"body"`
}
type FetchResponse struct {
Status int `json:"status"`
Body string `json:"body"`
Headers map[string][]string `json:"headers"`
}
type ErrorResponse struct {
Error string `json:"error"`
}
func writeError(w http.ResponseWriter, status int, msg string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(ErrorResponse{Error: msg})
}
func fetchHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeError(w, http.StatusMethodNotAllowed, "only POST allowed")
return
}
var req FetchRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON: "+err.Error())
return
}
if req.URL == "" {
writeError(w, http.StatusBadRequest, "url is required")
return
}
if req.Method == "" {
req.Method = "GET"
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
c := client.NewClient("chrome-145", client.WithTimeout(60*time.Second))
defer c.Close()
if req.Proxy != "" {
c.SetProxy(req.Proxy)
}
// Extract user-agent separately so httpcloak uses it instead of its preset value.
headers := make(map[string][]string, len(req.Headers))
var userAgent string
for k, v := range req.Headers {
lower := strings.ToLower(k)
if lower == "user-agent" {
userAgent = v
} else {
headers[k] = []string{v}
}
}
var bodyReader io.Reader
if req.Body != "" {
bodyReader = strings.NewReader(req.Body)
}
hcReq := &client.Request{
Method: strings.ToUpper(req.Method),
URL: req.URL,
Headers: headers,
Body: bodyReader,
UserAgent: userAgent,
FetchMode: client.FetchModeNavigate,
}
resp, err := c.Do(ctx, hcReq)
if err != nil {
writeError(w, http.StatusBadGateway, "fetch failed: "+err.Error())
return
}
body, err := resp.Text()
if err != nil {
writeError(w, http.StatusInternalServerError, "read body failed: "+err.Error())
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(FetchResponse{
Status: resp.StatusCode,
Body: body,
Headers: resp.Headers,
})
}
func main() {
const port = "7878"
mux := http.NewServeMux()
mux.HandleFunc("/fetch", fetchHandler)
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"status":"ok"}`)
})
log.Printf("TLS server (httpcloak chrome-145) listening on :%s", port)
log.Fatal(http.ListenAndServe(":"+port, mux))
}
Initialize and build
bash
go mod init tls-server
go get github.com/sardanioss/httpcloak/client
go build -o main main.go
Run with PM2
bash
pm2 start ./main --name tls-server
pm2 save
Verify it's running
bash
curl http://localhost:7878/health
Expected: {"status":"ok"}
Note: The TLS server must run on the same machine as your n8n instance. The n8n workflow calls it at
http://localhost:7878/fetch. If you're using n8n Cloud, you would need a self-hosted setup โ this is one reason self-hosted n8n is strongly recommended for Cloudflare Challenge scraping.
Step 2 โ Allow n8n to Call Localhost
By default, n8n blocks HTTP Request nodes from calling localhost addresses (SSRF protection). You need to disable this restriction.
Add the following environment variable to your n8n process and restart:
bash
N8N_BLOCK_ACCESS_TO_LOCALHOST=false pm2 restart n8n --update-env
Important: The
--update-envflag is required. A plainpm2 restart n8nwill not pick up new environment variables from your shell session.
Verify it took effect by checking the n8n process environment:
bash
cat /proc/$(pm2 jlist | python3 -c "import json,sys; procs=json.load(sys.stdin); print([p for p in procs if p['name']=='n8n'][0]['pid'])")/environ | tr '\0' '\n' | grep N8N_BLOCK
You should see N8N_BLOCK_ACCESS_TO_LOCALHOST=false.
The Cloudflare Challenge Workflow
The workflow has two parallel trigger paths โ schedule (runs every 6 hours automatically) and webhook (runs on demand via HTTP POST). Both paths follow identical logic and can be configured independently.

How It Works
Schedule path:
Every 6 Hours โ Set Target Config [Schedule] โ Solve Cloudflare Challenge
โ Prepare TLS Request โ Fetch via TLS Server โ Extract Result
Webhook path:
Webhook Trigger โ Set Target Config [Webhook] โ Solve Cloudflare Challenge
โ Prepare TLS Request โ Fetch via TLS Server โ Extract Result โ Respond to Webhook
Node Configuration
1. Set Target Config
This node stores the three values the rest of the workflow depends on. Edit these fields to point at your target.
For the Schedule path (Set Target Config [Schedule]):
| Field | Example Value | Description |
|---|---|---|
targetURL |
https://protected-site.com/page |
The Cloudflare-protected URL to scrape |
proxy |
host:port:user:pass |
Proxy in host:port:user:pass format โ used for both CapSolver and the TLS server |
For the Webhook path (Set Target Config [Webhook]), the values come from the POST body:
| Field | Expression | Description |
|---|---|---|
targetURL |
={{ $json.body.targetURL }} |
The Cloudflare-protected URL to scrape |
proxy |
={{ $json.body.proxy }} |
Proxy in host:port:user:pass format |
userAgent |
={{ $json.body.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36' }} |
User-Agent string โ defaults to a recent Chrome UA if not provided |
Single proxy field with auto-conversion: You only need to provide the proxy once in
host:port:user:passformat. The CapSolver node uses this format directly, and the Prepare TLS Request code node converts it internally tohttp://user:pass@host:portURL format for the TLS server using atoProxyURL()helper function. Both steps use the same proxy IP โ thecf_clearancecookie is bound to the IP that solved the challenge.
2. Solve Cloudflare Challenge (CapSolver Node)
| Parameter | Value |
|---|---|
| Operation | Cloudflare Challenge |
| Type | AntiCloudflareTask |
| Website URL | ={{ $json.targetURL }} |
| Proxy | ={{ $json.proxy }} |
| Credential | Select your CapSolver account |
| On Error | Continue (using regular output) |
The onError: "continueRegularOutput" setting means the workflow continues even if CapSolver fails to find a challenge (e.g., the page is not currently behind a challenge). The downstream code node checks whether a solution was returned and gracefully handles both cases.
CapSolver launches a real browser through your proxy, loads the target URL, and solves the Cloudflare challenge. When successful, it returns a solution object containing:
cookiesโ an object{ cf_clearance: "..." }with the clearance cookieuserAgentโ the exact User-Agent string the browser used during the solve
3. Prepare TLS Request (Code Node)
This node builds the request payload for the TLS server, converting the CapSolver solution into the right format.
For the Webhook path (Prepare TLS Request [Webhook]):
javascript
const config = $('Set Target Config [Webhook]').first().json;
const capResult = $input.first().json;
// Convert proxy from host:port:user:pass to http://user:pass@host:port
function toProxyURL(proxy) {
if (!proxy) return '';
if (proxy.startsWith('http')) return proxy;
const parts = proxy.split(':');
if (parts.length === 4) {
return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;
}
return proxy;
}
// Check if CapSolver succeeded or failed (continueOnFail)
let cookieStr = '';
let ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';
if (capResult.data && capResult.data.solution) {
const solution = capResult.data.solution;
const cookies = solution.cookies;
cookieStr = cookies && typeof cookies === 'object'
? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')
: (cookies || '');
if (solution.userAgent) ua = solution.userAgent;
}
const chromeMatch = ua.match(/Chrome\/(\d+)/);
const chromeVer = chromeMatch ? chromeMatch[1] : '145';
const secChUa = `"Chromium";v="${chromeVer}", "Not A(Brand";v="8", "Google Chrome";v="${chromeVer}"`;
const headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'accept-language': 'en-US,en;q=0.9',
'sec-ch-ua': secChUa,
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1',
'user-agent': ua
};
if (cookieStr) headers['cookie'] = cookieStr;
return [{ json: {
url: config.targetURL,
method: 'GET',
proxy: toProxyURL(config.proxy),
headers
}}];
For the Schedule path (Prepare TLS Request [Schedule]), the code is identical except it references a different config node:
javascript
const config = $('Set Target Config [Schedule]').first().json;
Key details about this code:
-
toProxyURL()converts the proxy format automatically. You store the proxy ashost:port:user:pass(which CapSolver expects), and the function converts it tohttp://user:pass@host:port(which the TLS server expects). No need to maintain two separate proxy fields. -
Graceful fallback when CapSolver fails. With
onError: "continueRegularOutput", the CapSolver node passes its output downstream even on failure. The code checkscapResult.data && capResult.data.solutionโ if there's no solution, it proceeds without cookies (useful when a page isn't currently showing a challenge). -
solution.cookiesis an object, not a string. CapSolver'sAntiCloudflareTaskreturns cookies as{ cf_clearance: "abc123..." }. This must be serialized to"cf_clearance=abc123..."before sending as a header. The code handles both cases. -
solution.userAgentmust be forwarded exactly. Thecf_clearancecookie is tied to the specific User-Agent that was used during the solve. If no userAgent is provided (or CapSolver failed), the code defaults to a recent Chrome UA. Thesec-ch-uaheader is dynamically derived from the Chrome version in the User-Agent string.
4. Fetch via TLS Server (HTTP Request Node)
| Setting | Value |
|---|---|
| Method | POST |
| URL | http://localhost:7878/fetch |
| Content Type | Raw |
| Raw Content Type | application/json |
| Body | ={{ JSON.stringify($json) }} |
| Timeout | 60000 ms |
Why
contentType: "raw"and not"json"? n8n'sjsoncontent type mode expects body parameters as key-value pairs. If you passJSON.stringify($json)as a string, n8n treats the whole string as a single malformed param and sends{"": ""}to the server. Usingrawmode sends the body exactly as the expression evaluates.
5. Extract Result
Pulls the three fields from the TLS server response into clean output fields:
| Field | Expression | Type |
|---|---|---|
status |
={{ $json.status }} |
number |
body |
={{ $json.body }} |
string |
fetchedAt |
={{ new Date().toISOString() }} |
string |
6. Respond to Webhook (Webhook path only)
| Setting | Value |
|---|---|
| Respond With | JSON |
| Response Body | ={{ JSON.stringify($json) }} |
Returns the extracted result as a JSON response to the caller.
Import This Workflow
Copy the JSON below and import it into n8n via Menu โ Import from JSON. After importing, update the Set Target Config [Schedule] node with your own targetURL and proxy (in host:port:user:pass format), and select your CapSolver credential in the two solver nodes. The webhook path reads its config from the POST body, so no placeholder editing is needed there.
Click to expand workflow JSON
json
{
"name": "Cloudflare Challenge โ CapSolver + Schedule + Webhook",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 6
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [-400, 0],
"id": "cf111111-1111-1111-1111-111111111101",
"name": "Every 6 Hours"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "cfg-s-001",
"name": "targetURL",
"value": "https://YOUR_CF_PROTECTED_SITE.com",
"type": "string"
},
{
"id": "cfg-s-002",
"name": "proxy",
"value": "YOUR_PROXY_HOST:PORT:USER:PASS",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-96, 0],
"id": "cf111111-1111-1111-1111-111111111102",
"name": "Set Target Config [Schedule]"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.targetURL }}",
"proxy": "={{ $json.proxy }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [208, 0],
"id": "cf111111-1111-1111-1111-111111111103",
"name": "Solve Cloudflare Challenge [Schedule]",
"credentials": {
"capSolverApi": {
"id": "YOUR_CREDENTIAL_ID",
"name": "CapSolver account"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const config = $('Set Target Config [Schedule]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n if (!proxy) return '';\n if (proxy.startsWith('http')) return proxy;\n const parts = proxy.split(':');\n if (parts.length === 4) return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n const solution = capResult.data.solution;\n const cookies = solution.cookies;\n cookieStr = cookies && typeof cookies === 'object'\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst headers = {\n 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n 'accept-language': 'en-US,en;q=0.9',\n 'sec-ch-ua': secChUa,\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'sec-fetch-dest': 'document',\n 'sec-fetch-mode': 'navigate',\n 'sec-fetch-site': 'none',\n 'sec-fetch-user': '?1',\n 'upgrade-insecure-requests': '1',\n 'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n url: config.targetURL,\n method: 'GET',\n proxy: toProxyURL(config.proxy),\n headers\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [512, 0],
"id": "cf111111-1111-1111-1111-111111111104",
"name": "Prepare TLS Request [Schedule]"
},
{
"parameters": {
"method": "POST",
"url": "http://localhost:7878/fetch",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify($json) }}",
"options": {
"timeout": 60000
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [800, 0],
"id": "cf111111-1111-1111-1111-111111111105",
"name": "Fetch via TLS Server [Schedule]"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "out-s-001",
"name": "status",
"value": "={{ $json.status }}",
"type": "number"
},
{
"id": "out-s-002",
"name": "body",
"value": "={{ $json.body }}",
"type": "string"
},
{
"id": "out-s-003",
"name": "fetchedAt",
"value": "={{ new Date().toISOString() }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1104, 0],
"id": "cf111111-1111-1111-1111-111111111106",
"name": "Extract Result [Schedule]"
},
{
"parameters": {
"httpMethod": "POST",
"path": "cloudflare-scraper",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [-400, 420],
"id": "cf111111-1111-1111-1111-111111111107",
"name": "Webhook Trigger",
"webhookId": "cf111111-aaaa-bbbb-cccc-111111111107",
"onError": "continueRegularOutput"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "cfg-w-001",
"name": "targetURL",
"value": "={{ $json.body.targetURL }}",
"type": "string"
},
{
"id": "cfg-w-002",
"name": "proxy",
"value": "={{ $json.body.proxy }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-96, 420],
"id": "cf111111-1111-1111-1111-111111111108",
"name": "Set Target Config [Webhook]"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.targetURL }}",
"proxy": "={{ $json.proxy }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [208, 420],
"id": "cf111111-1111-1111-1111-111111111109",
"name": "Solve Cloudflare Challenge [Webhook]",
"credentials": {
"capSolverApi": {
"id": "YOUR_CREDENTIAL_ID",
"name": "CapSolver account"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const config = $('Set Target Config [Webhook]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n if (!proxy) return '';\n if (proxy.startsWith('http')) return proxy;\n const parts = proxy.split(':');\n if (parts.length === 4) return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n const solution = capResult.data.solution;\n const cookies = solution.cookies;\n cookieStr = cookies && typeof cookies === 'object'\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst headers = {\n 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n 'accept-language': 'en-US,en;q=0.9',\n 'sec-ch-ua': secChUa,\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'sec-fetch-dest': 'document',\n 'sec-fetch-mode': 'navigate',\n 'sec-fetch-site': 'none',\n 'sec-fetch-user': '?1',\n 'upgrade-insecure-requests': '1',\n 'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n url: config.targetURL,\n method: 'GET',\n proxy: toProxyURL(config.proxy),\n headers\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [512, 420],
"id": "cf111111-1111-1111-1111-111111111110",
"name": "Prepare TLS Request [Webhook]"
},
{
"parameters": {
"method": "POST",
"url": "http://localhost:7878/fetch",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify($json) }}",
"options": {
"timeout": 60000
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [800, 420],
"id": "cf111111-1111-1111-1111-111111111111",
"name": "Fetch via TLS Server [Webhook]"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "out-w-001",
"name": "status",
"value": "={{ $json.status }}",
"type": "number"
},
{
"id": "out-w-002",
"name": "body",
"value": "={{ $json.body }}",
"type": "string"
},
{
"id": "out-w-003",
"name": "fetchedAt",
"value": "={{ new Date().toISOString() }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1104, 420],
"id": "cf111111-1111-1111-1111-111111111112",
"name": "Extract Result [Webhook]"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [1408, 420],
"id": "cf111111-1111-1111-1111-111111111113",
"name": "Respond to Webhook"
}
],
"connections": {
"Every 6 Hours": {
"main": [[{"node": "Set Target Config [Schedule]", "type": "main", "index": 0}]]
},
"Set Target Config [Schedule]": {
"main": [[{"node": "Solve Cloudflare Challenge [Schedule]", "type": "main", "index": 0}]]
},
"Solve Cloudflare Challenge [Schedule]": {
"main": [[{"node": "Prepare TLS Request [Schedule]", "type": "main", "index": 0}]]
},
"Prepare TLS Request [Schedule]": {
"main": [[{"node": "Fetch via TLS Server [Schedule]", "type": "main", "index": 0}]]
},
"Fetch via TLS Server [Schedule]": {
"main": [[{"node": "Extract Result [Schedule]", "type": "main", "index": 0}]]
},
"Webhook Trigger": {
"main": [[{"node": "Set Target Config [Webhook]", "type": "main", "index": 0}]]
},
"Set Target Config [Webhook]": {
"main": [[{"node": "Solve Cloudflare Challenge [Webhook]", "type": "main", "index": 0}]]
},
"Solve Cloudflare Challenge [Webhook]": {
"main": [[{"node": "Prepare TLS Request [Webhook]", "type": "main", "index": 0}]]
},
"Prepare TLS Request [Webhook]": {
"main": [[{"node": "Fetch via TLS Server [Webhook]", "type": "main", "index": 0}]]
},
"Fetch via TLS Server [Webhook]": {
"main": [[{"node": "Extract Result [Webhook]", "type": "main", "index": 0}]]
},
"Extract Result [Webhook]": {
"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
}
}
Test It
Once the TLS server is running and the workflow is active, trigger the webhook path:
bash
curl -X POST https://your-n8n-instance.com/webhook/cloudflare-scraper \
-H "Content-Type: application/json" \
-d '{
"targetURL": "https://protected-site.com",
"proxy": "host:port:user:pass"
}'
You can also pass a custom userAgent in the body โ if omitted, it defaults to a recent Chrome UA.
Expected response:
json
{
"status": 200,
"body": "<!DOCTYPE html><html>...",
"fetchedAt": "2026-03-10T19:30:00.000Z"
}
A status: 200 with real HTML content confirms the full pipeline worked โ Cloudflare challenge solved, TLS fingerprint passed, page content retrieved.
Use-Case Workflows
The basic scraper workflow above retrieves raw page content. The following workflows extend the same Cloudflare Challenge pattern โ Set Target Config โ Solve CF Challenge โ Prepare TLS Request โ Fetch via TLS Server โ to specific use cases. Each requires the same prerequisites: a self-hosted n8n instance, the TLS server running on port 7878, a residential proxy, and a CapSolver credential. The Solver API is the exception โ it doesn't need the TLS server since it returns the solution without fetching.
All of these templates are available in the repo's workflows/use-cases/ directory and are importable into n8n.
| Workflow | Purpose |
|---|---|
Cloudflare Challenge Site Health Monitor โ CapSolver + Schedule + Webhook |
Monitors a CF-protected site every 30 minutes, tracks uptime in $workflow.staticData, alerts after 2+ consecutive failures |
Cloudflare Challenge Price Monitor / Scrape Data โ CapSolver + Schedule + Webhook |
Scrapes price and product name from a CF-protected page every 6 hours, compares against previous values, alerts on changes |
Cloudflare Challenge Account Login โ CapSolver + Schedule + Webhook |
Logs into your own account on a CF-protected site by solving the challenge first, then POSTing credentials through the TLS server |
Cloudflare Challenge Account Signup โ CapSolver + Schedule + Webhook |
Registers with your own email on a CF-protected site, same two-phase approach as login |
Cloudflare Challenge โ Solver API |
Webhook-only API that returns raw cf_clearance + userAgent โ no page fetch, no TLS server needed. Your external app handles the fetching. |
Site Health Monitor
This workflow checks whether a Cloudflare-protected site is responding correctly. It runs every 30 minutes (schedule) or on demand (webhook), solves the Cloudflare Challenge, fetches the page through the TLS server, and evaluates the HTTP status code.
Schedule path:
Every 30 Minutes โ Set Target Config โ Solve CF Challenge โ Prepare TLS Request
โ Fetch via TLS Server โ Site Up? โ Record Uptime / Record Downtime
โ 2+ Consecutive Failures? โ Create Incident Alert / Skip Alert
The Site Up? node checks $json.status < 400 (note: the TLS server returns status, not statusCode). The uptime/downtime code nodes use $workflow.staticData to track:
- Total checks and successes
- Uptime percentage
- Consecutive failure count
- Alert suppression for single failures (avoids false positives)
An incident alert is only created after 2 or more consecutive failures, reducing noise from transient network issues.
Click to expand full workflow JSON
json
{
"name": "Cloudflare Challenge Site Health Monitor โ CapSolver + Schedule + Webhook",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 30
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [-600, 0],
"id": "cf555555-5555-5555-5555-555555555501",
"name": "Every 30 Minutes"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "cfg-s-001", "name": "targetURL", "value": "https://YOUR_CF_PROTECTED_SITE.com", "type": "string" },
{ "id": "cfg-s-002", "name": "proxy", "value": "YOUR_PROXY_HOST:PORT:USER:PASS", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-300, 0],
"id": "cf555555-5555-5555-5555-555555555502",
"name": "Set Target Config [Schedule]"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.targetURL }}",
"proxy": "={{ $json.proxy }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [0, 0],
"id": "cf555555-5555-5555-5555-555555555503",
"name": "Solve Cloudflare Challenge [Schedule]",
"onError": "continueRegularOutput",
"credentials": {
"capSolverApi": {
"id": "YOUR_CREDENTIAL_ID",
"name": "CapSolver account"
}
}
},
{
"parameters": {
"jsCode": "const config = $('Set Target Config [Schedule]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n if (!proxy) return '';\n if (proxy.startsWith('http')) return proxy;\n const parts = proxy.split(':');\n if (parts.length === 4) {\n return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n }\n return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n const solution = capResult.data.solution;\n const cookies = solution.cookies;\n cookieStr = cookies && typeof cookies === 'object'\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst headers = {\n 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n 'accept-language': 'en-US,en;q=0.9',\n 'sec-ch-ua': secChUa,\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'sec-fetch-dest': 'document',\n 'sec-fetch-mode': 'navigate',\n 'sec-fetch-site': 'none',\n 'sec-fetch-user': '?1',\n 'upgrade-insecure-requests': '1',\n 'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n url: config.targetURL,\n method: 'GET',\n proxy: toProxyURL(config.proxy),\n headers\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [300, 0],
"id": "cf555555-5555-5555-5555-555555555504",
"name": "Prepare TLS Request [Schedule]"
},
{
"parameters": {
"method": "POST",
"url": "http://localhost:7878/fetch",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify($json) }}",
"options": { "timeout": 60000 }
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [600, 0],
"id": "cf555555-5555-5555-5555-555555555505",
"name": "Fetch via TLS Server [Schedule]"
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 },
"conditions": [
{ "id": "health-if-001", "leftValue": "={{ $json.status }}", "rightValue": 400, "operator": { "type": "number", "operation": "lt" } }
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [900, 0],
"id": "cf555555-5555-5555-5555-555555555506",
"name": "Site Up?"
},
{
"parameters": {
"jsCode": "const staticData = $workflow.staticData;\nif (!staticData.checks) staticData.checks = 0;\nif (!staticData.successes) staticData.successes = 0;\n\nstaticData.checks++;\nstaticData.successes++;\nstaticData.lastSuccess = new Date().toISOString();\nstaticData.consecutiveFailures = 0;\n\nconst uptime = ((staticData.successes / staticData.checks) * 100).toFixed(2);\n\nreturn [{\n json: {\n status: 'up',\n statusCode: $input.first().json.status,\n uptime: `${uptime}%`,\n totalChecks: staticData.checks,\n checkedAt: new Date().toISOString()\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1200, -100],
"id": "cf555555-5555-5555-5555-555555555507",
"name": "Record Uptime"
},
{
"parameters": {
"jsCode": "const staticData = $workflow.staticData;\nif (!staticData.checks) staticData.checks = 0;\nif (!staticData.successes) staticData.successes = 0;\nif (!staticData.consecutiveFailures) staticData.consecutiveFailures = 0;\n\nstaticData.checks++;\nstaticData.consecutiveFailures++;\nstaticData.lastFailure = new Date().toISOString();\n\nconst uptime = ((staticData.successes / staticData.checks) * 100).toFixed(2);\n\nreturn [{\n json: {\n status: 'down',\n statusCode: $input.first().json.status,\n uptime: `${uptime}%`,\n consecutiveFailures: staticData.consecutiveFailures,\n totalChecks: staticData.checks,\n alertMessage: `ALERT: CF-protected site has been down for ${staticData.consecutiveFailures} consecutive checks (${staticData.consecutiveFailures * 30} minutes)`,\n checkedAt: new Date().toISOString()\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1200, 100],
"id": "cf555555-5555-5555-5555-555555555508",
"name": "Record Downtime"
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 },
"conditions": [
{ "id": "alert-if-001", "leftValue": "={{ $json.consecutiveFailures }}", "rightValue": 2, "operator": { "type": "number", "operation": "gte" } }
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [1500, 100],
"id": "cf555555-5555-5555-5555-555555555509",
"name": "2+ Consecutive Failures?"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "inc-001", "name": "incident", "value": "=INCIDENT: {{ $json.alertMessage }}", "type": "string" },
{ "id": "inc-002", "name": "severity", "value": "critical", "type": "string" },
{ "id": "inc-003", "name": "checkedAt", "value": "={{ $json.checkedAt }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1800, 0],
"id": "cf555555-5555-5555-5555-555555555510",
"name": "Create Incident Alert"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "skip-001", "name": "note", "value": "Single failure โ monitoring, no alert yet", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1800, 200],
"id": "cf555555-5555-5555-5555-555555555511",
"name": "Skip Alert (1st Failure)"
},
{
"parameters": {
"httpMethod": "POST",
"path": "cloudflare-site-health-monitor",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [-600, 500],
"id": "cf555555-5555-5555-5555-555555555512",
"name": "Webhook Trigger",
"webhookId": "cf555555-aaaa-bbbb-cccc-555555555512",
"onError": "continueRegularOutput"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "cfg-w-001", "name": "targetURL", "value": "={{ $json.body.targetURL }}", "type": "string" },
{ "id": "cfg-w-002", "name": "proxy", "value": "={{ $json.body.proxy }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-300, 500],
"id": "cf555555-5555-5555-5555-555555555513",
"name": "Set Target Config [Webhook]"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.targetURL }}",
"proxy": "={{ $json.proxy }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [0, 500],
"id": "cf555555-5555-5555-5555-555555555514",
"name": "Solve Cloudflare Challenge [Webhook]",
"onError": "continueRegularOutput",
"credentials": {
"capSolverApi": {
"id": "YOUR_CREDENTIAL_ID",
"name": "CapSolver account"
}
}
},
{
"parameters": {
"jsCode": "const config = $('Set Target Config [Webhook]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n if (!proxy) return '';\n if (proxy.startsWith('http')) return proxy;\n const parts = proxy.split(':');\n if (parts.length === 4) {\n return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n }\n return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n const solution = capResult.data.solution;\n const cookies = solution.cookies;\n cookieStr = cookies && typeof cookies === 'object'\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst headers = {\n 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n 'accept-language': 'en-US,en;q=0.9',\n 'sec-ch-ua': secChUa,\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'sec-fetch-dest': 'document',\n 'sec-fetch-mode': 'navigate',\n 'sec-fetch-site': 'none',\n 'sec-fetch-user': '?1',\n 'upgrade-insecure-requests': '1',\n 'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n url: config.targetURL,\n method: 'GET',\n proxy: toProxyURL(config.proxy),\n headers\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [300, 500],
"id": "cf555555-5555-5555-5555-555555555515",
"name": "Prepare TLS Request [Webhook]"
},
{
"parameters": {
"method": "POST",
"url": "http://localhost:7878/fetch",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify($json) }}",
"options": { "timeout": 60000 }
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [600, 500],
"id": "cf555555-5555-5555-5555-555555555516",
"name": "Fetch via TLS Server [Webhook]"
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 },
"conditions": [
{ "id": "health-if-002", "leftValue": "={{ $json.status }}", "rightValue": 400, "operator": { "type": "number", "operation": "lt" } }
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [900, 500],
"id": "cf555555-5555-5555-5555-555555555517",
"name": "Site Up? [Webhook]"
},
{
"parameters": {
"jsCode": "const staticData = $workflow.staticData;\nif (!staticData.checks) staticData.checks = 0;\nif (!staticData.successes) staticData.successes = 0;\n\nstaticData.checks++;\nstaticData.successes++;\nstaticData.lastSuccess = new Date().toISOString();\nstaticData.consecutiveFailures = 0;\n\nconst uptime = ((staticData.successes / staticData.checks) * 100).toFixed(2);\n\nreturn [{\n json: {\n status: 'up',\n statusCode: $input.first().json.status,\n uptime: `${uptime}%`,\n totalChecks: staticData.checks,\n checkedAt: new Date().toISOString()\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1200, 400],
"id": "cf555555-5555-5555-5555-555555555518",
"name": "Record Uptime [Webhook]"
},
{
"parameters": {
"jsCode": "const staticData = $workflow.staticData;\nif (!staticData.checks) staticData.checks = 0;\nif (!staticData.successes) staticData.successes = 0;\nif (!staticData.consecutiveFailures) staticData.consecutiveFailures = 0;\n\nstaticData.checks++;\nstaticData.consecutiveFailures++;\nstaticData.lastFailure = new Date().toISOString();\n\nconst uptime = ((staticData.successes / staticData.checks) * 100).toFixed(2);\n\nreturn [{\n json: {\n status: 'down',\n statusCode: $input.first().json.status,\n uptime: `${uptime}%`,\n consecutiveFailures: staticData.consecutiveFailures,\n totalChecks: staticData.checks,\n alertMessage: `ALERT: CF-protected site has been down for ${staticData.consecutiveFailures} consecutive checks (${staticData.consecutiveFailures * 30} minutes)`,\n checkedAt: new Date().toISOString()\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1200, 600],
"id": "cf555555-5555-5555-5555-555555555519",
"name": "Record Downtime [Webhook]"
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 },
"conditions": [
{ "id": "alert-if-002", "leftValue": "={{ $json.consecutiveFailures }}", "rightValue": 2, "operator": { "type": "number", "operation": "gte" } }
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [1500, 600],
"id": "cf555555-5555-5555-5555-555555555520",
"name": "2+ Consecutive Failures? [Webhook]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "inc-004", "name": "incident", "value": "=INCIDENT: {{ $json.alertMessage }}", "type": "string" },
{ "id": "inc-005", "name": "severity", "value": "critical", "type": "string" },
{ "id": "inc-006", "name": "checkedAt", "value": "={{ $json.checkedAt }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1800, 500],
"id": "cf555555-5555-5555-5555-555555555521",
"name": "Create Incident Alert [Webhook]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "skip-002", "name": "note", "value": "Single failure โ monitoring, no alert yet", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1800, 700],
"id": "cf555555-5555-5555-5555-555555555522",
"name": "Skip Alert (1st Failure) [Webhook]"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [2100, 500],
"id": "cf555555-5555-5555-5555-555555555523",
"name": "Respond to Webhook"
}
],
"connections": {
"Every 30 Minutes": {
"main": [[{"node": "Set Target Config [Schedule]", "type": "main", "index": 0}]]
},
"Set Target Config [Schedule]": {
"main": [[{"node": "Solve Cloudflare Challenge [Schedule]", "type": "main", "index": 0}]]
},
"Solve Cloudflare Challenge [Schedule]": {
"main": [[{"node": "Prepare TLS Request [Schedule]", "type": "main", "index": 0}]]
},
"Prepare TLS Request [Schedule]": {
"main": [[{"node": "Fetch via TLS Server [Schedule]", "type": "main", "index": 0}]]
},
"Fetch via TLS Server [Schedule]": {
"main": [[{"node": "Site Up?", "type": "main", "index": 0}]]
},
"Site Up?": {
"main": [
[{"node": "Record Uptime", "type": "main", "index": 0}],
[{"node": "Record Downtime", "type": "main", "index": 0}]
]
},
"Record Downtime": {
"main": [[{"node": "2+ Consecutive Failures?", "type": "main", "index": 0}]]
},
"2+ Consecutive Failures?": {
"main": [
[{"node": "Create Incident Alert", "type": "main", "index": 0}],
[{"node": "Skip Alert (1st Failure)", "type": "main", "index": 0}]
]
},
"Webhook Trigger": {
"main": [[{"node": "Set Target Config [Webhook]", "type": "main", "index": 0}]]
},
"Set Target Config [Webhook]": {
"main": [[{"node": "Solve Cloudflare Challenge [Webhook]", "type": "main", "index": 0}]]
},
"Solve Cloudflare Challenge [Webhook]": {
"main": [[{"node": "Prepare TLS Request [Webhook]", "type": "main", "index": 0}]]
},
"Prepare TLS Request [Webhook]": {
"main": [[{"node": "Fetch via TLS Server [Webhook]", "type": "main", "index": 0}]]
},
"Fetch via TLS Server [Webhook]": {
"main": [[{"node": "Site Up? [Webhook]", "type": "main", "index": 0}]]
},
"Site Up? [Webhook]": {
"main": [
[{"node": "Record Uptime [Webhook]", "type": "main", "index": 0}],
[{"node": "Record Downtime [Webhook]", "type": "main", "index": 0}]
]
},
"Record Downtime [Webhook]": {
"main": [[{"node": "2+ Consecutive Failures? [Webhook]", "type": "main", "index": 0}]]
},
"2+ Consecutive Failures? [Webhook]": {
"main": [
[{"node": "Create Incident Alert [Webhook]", "type": "main", "index": 0}],
[{"node": "Skip Alert (1st Failure) [Webhook]", "type": "main", "index": 0}]
]
},
"Record Uptime [Webhook]": {
"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]
},
"Create Incident Alert [Webhook]": {
"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]
},
"Skip Alert (1st Failure) [Webhook]": {
"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]
}
},
"active": false,
"settings": { "executionOrder": "v1" }
}
Also available at
workflows/use-cases/cloudflare-challenge-site-health-monitor.json.
Price Monitor
This workflow scrapes price and product data from a Cloudflare-protected page. It runs every 6 hours or on demand, compares the extracted price against the previous value stored in $workflow.staticData, and returns an alert payload if the price changed.
Schedule path:
Every 6 Hours โ Set Target Config โ Solve CF Challenge โ Prepare TLS Request
โ Fetch via TLS Server โ Extract Price โ Compare Price
โ Price Changed? โ Build Alert / No Change
Critical detail โ HTML extraction uses body, not data:
The reCAPTCHA version of this workflow uses dataPropertyName: "data" because n8n's HTTP Request node puts the response body in $json.data. The Cloudflare version fetches through the TLS server, which returns { status, body, headers } โ so the HTML content is in $json.body. The HTML extraction node must use dataPropertyName: "body".
json
{
"operation": "extractHtmlContent",
"sourceData": "json",
"dataPropertyName": "body",
"extractionValues": {
"values": [
{ "key": "price", "cssSelector": ".product-price, [data-price], .price" },
{ "key": "productName", "cssSelector": "h1, .product-title" }
]
}
}
Click to expand full workflow JSON
json
{
"name": "Cloudflare Challenge Price Monitor / Scrape Data โ CapSolver + Schedule + Webhook",
"nodes": [
{
"parameters": {
"rule": { "interval": [{ "field": "hours", "hoursInterval": 6 }] }
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [-500, 0],
"id": "cf333333-3333-3333-3333-333333333301",
"name": "Every 6 Hours"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "cfg-s-001", "name": "targetURL", "value": "https://YOUR_CF_PROTECTED_SITE.com/product-page", "type": "string" },
{ "id": "cfg-s-002", "name": "proxy", "value": "YOUR_PROXY_HOST:PORT:USER:PASS", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-200, 0],
"id": "cf333333-3333-3333-3333-333333333302",
"name": "Set Target Config [Schedule]"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.targetURL }}",
"proxy": "={{ $json.proxy }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [100, 0],
"id": "cf333333-3333-3333-3333-333333333303",
"name": "Solve Cloudflare Challenge [Schedule]",
"onError": "continueRegularOutput",
"credentials": { "capSolverApi": { "id": "YOUR_CREDENTIAL_ID", "name": "CapSolver account" } }
},
{
"parameters": {
"jsCode": "const config = $('Set Target Config [Schedule]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n if (!proxy) return '';\n if (proxy.startsWith('http')) return proxy;\n const parts = proxy.split(':');\n if (parts.length === 4) {\n return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n }\n return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n const solution = capResult.data.solution;\n const cookies = solution.cookies;\n cookieStr = cookies && typeof cookies === 'object'\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst headers = {\n 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n 'accept-language': 'en-US,en;q=0.9',\n 'sec-ch-ua': secChUa,\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'sec-fetch-dest': 'document',\n 'sec-fetch-mode': 'navigate',\n 'sec-fetch-site': 'none',\n 'sec-fetch-user': '?1',\n 'upgrade-insecure-requests': '1',\n 'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n url: config.targetURL,\n method: 'GET',\n proxy: toProxyURL(config.proxy),\n headers\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [400, 0],
"id": "cf333333-3333-3333-3333-333333333304",
"name": "Prepare TLS Request [Schedule]"
},
{
"parameters": {
"method": "POST",
"url": "http://localhost:7878/fetch",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify($json) }}",
"options": { "timeout": 60000 }
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [700, 0],
"id": "cf333333-3333-3333-3333-333333333305",
"name": "Fetch via TLS Server [Schedule]"
},
{
"parameters": {
"operation": "extractHtmlContent",
"sourceData": "json",
"dataPropertyName": "body",
"extractionValues": {
"values": [
{ "key": "price", "cssSelector": ".product-price, [data-price], .price", "returnValue": "text", "returnArray": false },
{ "key": "productName", "cssSelector": "h1, .product-title", "returnValue": "text", "returnArray": false }
]
},
"options": {}
},
"type": "n8n-nodes-base.html",
"typeVersion": 1.2,
"position": [1000, 0],
"id": "cf333333-3333-3333-3333-333333333306",
"name": "Extract Price"
},
{
"parameters": {
"jsCode": "const staticData = $workflow.staticData;\nconst currentPrice = $input.first().json.price;\nconst previousPrice = staticData.lastPrice;\nconst productName = $input.first().json.productName || 'Product';\n\nconst parsePrice = (str) => {\n if (!str) return null;\n const match = str.match(/[\\d,]+\\.?\\d*/);\n return match ? parseFloat(match[0].replace(',', '')) : null;\n};\n\nconst currentNum = parsePrice(currentPrice);\nconst previousNum = parsePrice(previousPrice);\n\nstaticData.lastPrice = currentPrice;\nstaticData.lastChecked = new Date().toISOString();\n\nconst changed = previousNum !== null && currentNum !== null && currentNum !== previousNum;\nconst direction = changed ? (currentNum < previousNum ? 'dropped' : 'increased') : 'unchanged';\nconst diff = changed ? Math.abs(currentNum - previousNum).toFixed(2) : '0';\n\nreturn [{\n json: {\n productName,\n currentPrice,\n previousPrice: previousPrice || 'first check',\n changed,\n direction,\n diff: changed ? `$${diff}` : null,\n checkedAt: new Date().toISOString()\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1300, 0],
"id": "cf333333-3333-3333-3333-333333333307",
"name": "Compare Price"
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 },
"conditions": [
{ "id": "price-if-001", "leftValue": "={{ $json.changed }}", "operator": { "type": "boolean", "operation": "true", "singleValue": true } }
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [1600, 0],
"id": "cf333333-3333-3333-3333-333333333308",
"name": "Price Changed?"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "alert-001", "name": "alert", "value": "=Price {{ $json.direction }} for {{ $json.productName }}: {{ $json.previousPrice }} โ {{ $json.currentPrice }} ({{ $json.direction === 'dropped' ? '-' : '+' }}{{ $json.diff }})", "type": "string" },
{ "id": "alert-002", "name": "severity", "value": "={{ $json.direction === 'dropped' ? 'deal' : 'info' }}", "type": "string" },
{ "id": "alert-003", "name": "checkedAt", "value": "={{ $json.checkedAt }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1900, -80],
"id": "cf333333-3333-3333-3333-333333333309",
"name": "Build Alert"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "nc-001", "name": "status", "value": "no_change", "type": "string" },
{ "id": "nc-002", "name": "currentPrice", "value": "={{ $json.currentPrice }}", "type": "string" },
{ "id": "nc-003", "name": "checkedAt", "value": "={{ $json.checkedAt }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1900, 128],
"id": "cf333333-3333-3333-3333-333333333310",
"name": "No Change"
},
{
"parameters": {
"httpMethod": "POST",
"path": "cloudflare-price-monitor",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [-500, 500],
"id": "cf333333-3333-3333-3333-333333333311",
"name": "Webhook Trigger",
"webhookId": "cf333333-aaaa-bbbb-cccc-333333333311",
"onError": "continueRegularOutput"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "cfg-w-001", "name": "targetURL", "value": "={{ $json.body.targetURL }}", "type": "string" },
{ "id": "cfg-w-002", "name": "proxy", "value": "={{ $json.body.proxy }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-200, 500],
"id": "cf333333-3333-3333-3333-333333333312",
"name": "Set Target Config [Webhook]"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.targetURL }}",
"proxy": "={{ $json.proxy }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [100, 500],
"id": "cf333333-3333-3333-3333-333333333313",
"name": "Solve Cloudflare Challenge [Webhook]",
"onError": "continueRegularOutput",
"credentials": { "capSolverApi": { "id": "YOUR_CREDENTIAL_ID", "name": "CapSolver account" } }
},
{
"parameters": {
"jsCode": "const config = $('Set Target Config [Webhook]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n if (!proxy) return '';\n if (proxy.startsWith('http')) return proxy;\n const parts = proxy.split(':');\n if (parts.length === 4) {\n return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n }\n return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n const solution = capResult.data.solution;\n const cookies = solution.cookies;\n cookieStr = cookies && typeof cookies === 'object'\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst headers = {\n 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n 'accept-language': 'en-US,en;q=0.9',\n 'sec-ch-ua': secChUa,\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'sec-fetch-dest': 'document',\n 'sec-fetch-mode': 'navigate',\n 'sec-fetch-site': 'none',\n 'sec-fetch-user': '?1',\n 'upgrade-insecure-requests': '1',\n 'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n url: config.targetURL,\n method: 'GET',\n proxy: toProxyURL(config.proxy),\n headers\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [400, 500],
"id": "cf333333-3333-3333-3333-333333333314",
"name": "Prepare TLS Request [Webhook]"
},
{
"parameters": {
"method": "POST",
"url": "http://localhost:7878/fetch",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify($json) }}",
"options": { "timeout": 60000 }
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [700, 500],
"id": "cf333333-3333-3333-3333-333333333315",
"name": "Fetch via TLS Server [Webhook]"
},
{
"parameters": {
"operation": "extractHtmlContent",
"sourceData": "json",
"dataPropertyName": "body",
"extractionValues": {
"values": [
{ "key": "price", "cssSelector": ".product-price, [data-price], .price", "returnValue": "text", "returnArray": false },
{ "key": "productName", "cssSelector": "h1, .product-title", "returnValue": "text", "returnArray": false }
]
},
"options": {}
},
"type": "n8n-nodes-base.html",
"typeVersion": 1.2,
"position": [1000, 500],
"id": "cf333333-3333-3333-3333-333333333316",
"name": "Extract Price [Webhook]"
},
{
"parameters": {
"jsCode": "const staticData = $workflow.staticData;\nconst currentPrice = $input.first().json.price;\nconst previousPrice = staticData.lastPrice;\nconst productName = $input.first().json.productName || 'Product';\n\nconst parsePrice = (str) => {\n if (!str) return null;\n const match = str.match(/[\\d,]+\\.?\\d*/);\n return match ? parseFloat(match[0].replace(',', '')) : null;\n};\n\nconst currentNum = parsePrice(currentPrice);\nconst previousNum = parsePrice(previousPrice);\n\nstaticData.lastPrice = currentPrice;\nstaticData.lastChecked = new Date().toISOString();\n\nconst changed = previousNum !== null && currentNum !== null && currentNum !== previousNum;\nconst direction = changed ? (currentNum < previousNum ? 'dropped' : 'increased') : 'unchanged';\nconst diff = changed ? Math.abs(currentNum - previousNum).toFixed(2) : '0';\n\nreturn [{\n json: {\n productName,\n currentPrice,\n previousPrice: previousPrice || 'first check',\n changed,\n direction,\n diff: changed ? `$${diff}` : null,\n checkedAt: new Date().toISOString()\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1300, 500],
"id": "cf333333-3333-3333-3333-333333333317",
"name": "Compare Price [Webhook]"
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 },
"conditions": [
{ "id": "price-if-002", "leftValue": "={{ $json.changed }}", "operator": { "type": "boolean", "operation": "true", "singleValue": true } }
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [1600, 500],
"id": "cf333333-3333-3333-3333-333333333318",
"name": "Price Changed? [Webhook]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "alert-004", "name": "alert", "value": "=Price {{ $json.direction }} for {{ $json.productName }}: {{ $json.previousPrice }} โ {{ $json.currentPrice }} ({{ $json.direction === 'dropped' ? '-' : '+' }}{{ $json.diff }})", "type": "string" },
{ "id": "alert-005", "name": "severity", "value": "={{ $json.direction === 'dropped' ? 'deal' : 'info' }}", "type": "string" },
{ "id": "alert-006", "name": "checkedAt", "value": "={{ $json.checkedAt }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1900, 420],
"id": "cf333333-3333-3333-3333-333333333319",
"name": "Build Alert [Webhook]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "nc-004", "name": "status", "value": "no_change", "type": "string" },
{ "id": "nc-005", "name": "currentPrice", "value": "={{ $json.currentPrice }}", "type": "string" },
{ "id": "nc-006", "name": "checkedAt", "value": "={{ $json.checkedAt }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1900, 628],
"id": "cf333333-3333-3333-3333-333333333320",
"name": "No Change [Webhook]"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [2200, 500],
"id": "cf333333-3333-3333-3333-333333333321",
"name": "Respond to Webhook"
}
],
"connections": {
"Every 6 Hours": {
"main": [[{"node": "Set Target Config [Schedule]", "type": "main", "index": 0}]]
},
"Set Target Config [Schedule]": {
"main": [[{"node": "Solve Cloudflare Challenge [Schedule]", "type": "main", "index": 0}]]
},
"Solve Cloudflare Challenge [Schedule]": {
"main": [[{"node": "Prepare TLS Request [Schedule]", "type": "main", "index": 0}]]
},
"Prepare TLS Request [Schedule]": {
"main": [[{"node": "Fetch via TLS Server [Schedule]", "type": "main", "index": 0}]]
},
"Fetch via TLS Server [Schedule]": {
"main": [[{"node": "Extract Price", "type": "main", "index": 0}]]
},
"Extract Price": {
"main": [[{"node": "Compare Price", "type": "main", "index": 0}]]
},
"Compare Price": {
"main": [[{"node": "Price Changed?", "type": "main", "index": 0}]]
},
"Price Changed?": {
"main": [
[{"node": "Build Alert", "type": "main", "index": 0}],
[{"node": "No Change", "type": "main", "index": 0}]
]
},
"Webhook Trigger": {
"main": [[{"node": "Set Target Config [Webhook]", "type": "main", "index": 0}]]
},
"Set Target Config [Webhook]": {
"main": [[{"node": "Solve Cloudflare Challenge [Webhook]", "type": "main", "index": 0}]]
},
"Solve Cloudflare Challenge [Webhook]": {
"main": [[{"node": "Prepare TLS Request [Webhook]", "type": "main", "index": 0}]]
},
"Prepare TLS Request [Webhook]": {
"main": [[{"node": "Fetch via TLS Server [Webhook]", "type": "main", "index": 0}]]
},
"Fetch via TLS Server [Webhook]": {
"main": [[{"node": "Extract Price [Webhook]", "type": "main", "index": 0}]]
},
"Extract Price [Webhook]": {
"main": [[{"node": "Compare Price [Webhook]", "type": "main", "index": 0}]]
},
"Compare Price [Webhook]": {
"main": [[{"node": "Price Changed? [Webhook]", "type": "main", "index": 0}]]
},
"Price Changed? [Webhook]": {
"main": [
[{"node": "Build Alert [Webhook]", "type": "main", "index": 0}],
[{"node": "No Change [Webhook]", "type": "main", "index": 0}]
]
},
"Build Alert [Webhook]": {
"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]
},
"No Change [Webhook]": {
"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]
}
},
"active": false,
"settings": { "executionOrder": "v1" }
}
Also available at
workflows/use-cases/cloudflare-challenge-price-monitor-scheduled.json.
Account Login
This workflow logs into your own account on a Cloudflare-protected site. It's a two-phase approach: first solve the Cloudflare Challenge to get cf_clearance + userAgent, then POST the login form through the TLS server with those credentials attached.
Schedule path:
Every 24 Hours โ Set Login Config โ Solve CF Challenge
โ Prepare TLS Login Request โ Submit Login via TLS Server
โ Login Successful? โ Mark Success / Mark Failed
The key difference from reCAPTCHA login: The reCAPTCHA version submits a g-recaptcha-response token in the form body. The Cloudflare version doesn't submit a token at all โ instead, it sends the cf_clearance cookie and the exact userAgent from the solve as HTTP headers. The form body contains only the login credentials.
Prepare TLS Login Request code:
javascript
const config = $('Set Login Config [Schedule]').first().json;
const capResult = $input.first().json;
// Convert proxy from host:port:user:pass to http://user:pass@host:port
function toProxyURL(proxy) {
if (!proxy) return '';
if (proxy.startsWith('http')) return proxy;
const parts = proxy.split(':');
if (parts.length === 4) return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;
return proxy;
}
let cookieStr = '';
let ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';
if (capResult.data && capResult.data.solution) {
const solution = capResult.data.solution;
const cookies = solution.cookies;
cookieStr = cookies && typeof cookies === 'object'
? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')
: (cookies || '');
if (solution.userAgent) ua = solution.userAgent;
}
const chromeMatch = ua.match(/Chrome\/(\d+)/);
const chromeVer = chromeMatch ? chromeMatch[1] : '145';
const secChUa = `"Chromium";v="${chromeVer}", "Not A(Brand";v="8", "Google Chrome";v="${chromeVer}"`;
const params = new URLSearchParams();
params.set(config.usernameField, config.usernameValue);
params.set(config.passwordField, config.passwordValue);
const headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'accept-language': 'en-US,en;q=0.9',
'content-type': 'application/x-www-form-urlencoded',
'sec-ch-ua': secChUa,
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'same-origin',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1',
'user-agent': ua
};
if (cookieStr) headers['cookie'] = cookieStr;
return [{ json: {
url: config.loginActionURL,
method: 'POST',
proxy: toProxyURL(config.proxy),
headers,
body: params.toString()
}}];
The Login Successful? node checks both the TLS server response status and whether the response body contains the configured successMarker string (e.g., "account-dashboard").
Click to expand full workflow JSON
json
{
"name": "Cloudflare Challenge Account Login โ CapSolver + Schedule + Webhook",
"nodes": [
{
"parameters": {
"rule": { "interval": [{ "field": "hours", "hoursInterval": 24 }] }
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [-640, 0],
"id": "cf666666-6666-6666-6666-666666666601",
"name": "Every 24 Hours"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "login-001", "name": "targetURL", "value": "https://YOUR_CF_PROTECTED_SITE.com/login", "type": "string" },
{ "id": "login-002", "name": "loginActionURL", "value": "https://YOUR_CF_PROTECTED_SITE.com/login", "type": "string" },
{ "id": "login-003", "name": "proxy", "value": "YOUR_PROXY_HOST:PORT:USER:PASS", "type": "string" },
{ "id": "login-004", "name": "usernameField", "value": "email", "type": "string" },
{ "id": "login-005", "name": "passwordField", "value": "password", "type": "string" },
{ "id": "login-006", "name": "usernameValue", "value": "[email protected]", "type": "string" },
{ "id": "login-007", "name": "passwordValue", "value": "YOUR_ACCOUNT_PASSWORD", "type": "string" },
{ "id": "login-008", "name": "successMarker", "value": "account-dashboard", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-336, 0],
"id": "cf666666-6666-6666-6666-666666666602",
"name": "Set Login Config [Schedule]"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.targetURL }}",
"proxy": "={{ $json.proxy }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [-32, 0],
"id": "cf666666-6666-6666-6666-666666666603",
"name": "Solve Cloudflare Challenge [Schedule]",
"credentials": { "capSolverApi": { "id": "YOUR_CREDENTIAL_ID", "name": "CapSolver account" } },
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const config = $('Set Login Config [Schedule]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n if (!proxy) return '';\n if (proxy.startsWith('http')) return proxy;\n const parts = proxy.split(':');\n if (parts.length === 4) {\n return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n }\n return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n const solution = capResult.data.solution;\n const cookies = solution.cookies;\n cookieStr = cookies && typeof cookies === 'object'\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst params = new URLSearchParams();\nparams.set(config.usernameField, config.usernameValue);\nparams.set(config.passwordField, config.passwordValue);\n\nconst headers = {\n 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n 'accept-language': 'en-US,en;q=0.9',\n 'content-type': 'application/x-www-form-urlencoded',\n 'sec-ch-ua': secChUa,\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'sec-fetch-dest': 'document',\n 'sec-fetch-mode': 'navigate',\n 'sec-fetch-site': 'same-origin',\n 'sec-fetch-user': '?1',\n 'upgrade-insecure-requests': '1',\n 'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n url: config.loginActionURL,\n method: 'POST',\n proxy: toProxyURL(config.proxy),\n headers,\n body: params.toString()\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [272, 0],
"id": "cf666666-6666-6666-6666-666666666604",
"name": "Prepare TLS Login Request [Schedule]"
},
{
"parameters": {
"method": "POST",
"url": "http://localhost:7878/fetch",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify($json) }}",
"options": { "timeout": 60000 }
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [576, 0],
"id": "cf666666-6666-6666-6666-666666666605",
"name": "Submit Login via TLS Server [Schedule]"
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": false, "leftValue": "", "typeValidation": "strict", "version": 2 },
"conditions": [
{ "id": "login-if-001", "leftValue": "={{ $json.status < 400 && String($json.body || '').includes($('Set Login Config [Schedule]').item.json.successMarker) }}", "operator": { "type": "boolean", "operation": "true", "singleValue": true } }
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [880, 0],
"id": "cf666666-6666-6666-6666-666666666606",
"name": "Login Successful? [Schedule]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "login-010", "name": "action", "value": "account_login", "type": "string" },
{ "id": "login-011", "name": "status", "value": "success", "type": "string" },
{ "id": "login-012", "name": "message", "value": "Configured account login flow succeeded (via Cloudflare Challenge bypass)", "type": "string" },
{ "id": "login-013", "name": "checkedAt", "value": "={{ new Date().toISOString() }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1184, -80],
"id": "cf666666-6666-6666-6666-666666666607",
"name": "Mark Login Success [Schedule]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "login-014", "name": "action", "value": "account_login", "type": "string" },
{ "id": "login-015", "name": "status", "value": "failed", "type": "string" },
{ "id": "login-016", "name": "statusCode", "value": "={{ $json.status }}", "type": "number" },
{ "id": "login-017", "name": "message", "value": "Login response did not match the configured success marker", "type": "string" },
{ "id": "login-018", "name": "checkedAt", "value": "={{ new Date().toISOString() }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1184, 120],
"id": "cf666666-6666-6666-6666-666666666608",
"name": "Mark Login Failed [Schedule]"
},
{
"parameters": {
"httpMethod": "POST",
"path": "cloudflare-account-login",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [-640, 420],
"id": "cf666666-6666-6666-6666-666666666609",
"name": "Webhook Trigger",
"webhookId": "cf666666-aaaa-bbbb-cccc-666666666609",
"onError": "continueRegularOutput"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "login-019", "name": "targetURL", "value": "={{ $json.body.targetURL }}", "type": "string" },
{ "id": "login-020", "name": "loginActionURL", "value": "={{ $json.body.loginActionURL }}", "type": "string" },
{ "id": "login-021", "name": "proxy", "value": "={{ $json.body.proxy }}", "type": "string" },
{ "id": "login-022", "name": "usernameField", "value": "={{ $json.body.usernameField }}", "type": "string" },
{ "id": "login-023", "name": "passwordField", "value": "={{ $json.body.passwordField }}", "type": "string" },
{ "id": "login-024", "name": "usernameValue", "value": "={{ $json.body.usernameValue }}", "type": "string" },
{ "id": "login-025", "name": "passwordValue", "value": "={{ $json.body.passwordValue }}", "type": "string" },
{ "id": "login-026", "name": "successMarker", "value": "={{ $json.body.successMarker }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-336, 420],
"id": "cf666666-6666-6666-6666-666666666610",
"name": "Set Login Config [Webhook]"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.targetURL }}",
"proxy": "={{ $json.proxy }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [-32, 420],
"id": "cf666666-6666-6666-6666-666666666611",
"name": "Solve Cloudflare Challenge [Webhook]",
"credentials": { "capSolverApi": { "id": "YOUR_CREDENTIAL_ID", "name": "CapSolver account" } },
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const config = $('Set Login Config [Webhook]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n if (!proxy) return '';\n if (proxy.startsWith('http')) return proxy;\n const parts = proxy.split(':');\n if (parts.length === 4) {\n return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n }\n return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n const solution = capResult.data.solution;\n const cookies = solution.cookies;\n cookieStr = cookies && typeof cookies === 'object'\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst params = new URLSearchParams();\nparams.set(config.usernameField, config.usernameValue);\nparams.set(config.passwordField, config.passwordValue);\n\nconst headers = {\n 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n 'accept-language': 'en-US,en;q=0.9',\n 'content-type': 'application/x-www-form-urlencoded',\n 'sec-ch-ua': secChUa,\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'sec-fetch-dest': 'document',\n 'sec-fetch-mode': 'navigate',\n 'sec-fetch-site': 'same-origin',\n 'sec-fetch-user': '?1',\n 'upgrade-insecure-requests': '1',\n 'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n url: config.loginActionURL,\n method: 'POST',\n proxy: toProxyURL(config.proxy),\n headers,\n body: params.toString()\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [272, 420],
"id": "cf666666-6666-6666-6666-666666666612",
"name": "Prepare TLS Login Request [Webhook]"
},
{
"parameters": {
"method": "POST",
"url": "http://localhost:7878/fetch",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify($json) }}",
"options": { "timeout": 60000 }
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [576, 420],
"id": "cf666666-6666-6666-6666-666666666613",
"name": "Submit Login via TLS Server [Webhook]"
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": false, "leftValue": "", "typeValidation": "strict", "version": 2 },
"conditions": [
{ "id": "login-if-002", "leftValue": "={{ $json.status < 400 && String($json.body || '').includes($('Set Login Config [Webhook]').item.json.successMarker) }}", "operator": { "type": "boolean", "operation": "true", "singleValue": true } }
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [880, 420],
"id": "cf666666-6666-6666-6666-666666666614",
"name": "Login Successful? [Webhook]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "login-028", "name": "action", "value": "account_login", "type": "string" },
{ "id": "login-029", "name": "status", "value": "success", "type": "string" },
{ "id": "login-030", "name": "message", "value": "Configured account login flow succeeded (via Cloudflare Challenge bypass)", "type": "string" },
{ "id": "login-031", "name": "checkedAt", "value": "={{ new Date().toISOString() }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1184, 340],
"id": "cf666666-6666-6666-6666-666666666615",
"name": "Mark Login Success [Webhook]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "login-032", "name": "action", "value": "account_login", "type": "string" },
{ "id": "login-033", "name": "status", "value": "failed", "type": "string" },
{ "id": "login-034", "name": "statusCode", "value": "={{ $json.status }}", "type": "number" },
{ "id": "login-035", "name": "message", "value": "Login response did not match the configured success marker", "type": "string" },
{ "id": "login-036", "name": "checkedAt", "value": "={{ new Date().toISOString() }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1184, 540],
"id": "cf666666-6666-6666-6666-666666666616",
"name": "Mark Login Failed [Webhook]"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [1488, 420],
"id": "cf666666-6666-6666-6666-666666666617",
"name": "Respond to Webhook"
}
],
"connections": {
"Every 24 Hours": {
"main": [[{"node": "Set Login Config [Schedule]", "type": "main", "index": 0}]]
},
"Set Login Config [Schedule]": {
"main": [[{"node": "Solve Cloudflare Challenge [Schedule]", "type": "main", "index": 0}]]
},
"Solve Cloudflare Challenge [Schedule]": {
"main": [[{"node": "Prepare TLS Login Request [Schedule]", "type": "main", "index": 0}]]
},
"Prepare TLS Login Request [Schedule]": {
"main": [[{"node": "Submit Login via TLS Server [Schedule]", "type": "main", "index": 0}]]
},
"Submit Login via TLS Server [Schedule]": {
"main": [[{"node": "Login Successful? [Schedule]", "type": "main", "index": 0}]]
},
"Login Successful? [Schedule]": {
"main": [
[{"node": "Mark Login Success [Schedule]", "type": "main", "index": 0}],
[{"node": "Mark Login Failed [Schedule]", "type": "main", "index": 0}]
]
},
"Webhook Trigger": {
"main": [[{"node": "Set Login Config [Webhook]", "type": "main", "index": 0}]]
},
"Set Login Config [Webhook]": {
"main": [[{"node": "Solve Cloudflare Challenge [Webhook]", "type": "main", "index": 0}]]
},
"Solve Cloudflare Challenge [Webhook]": {
"main": [[{"node": "Prepare TLS Login Request [Webhook]", "type": "main", "index": 0}]]
},
"Prepare TLS Login Request [Webhook]": {
"main": [[{"node": "Submit Login via TLS Server [Webhook]", "type": "main", "index": 0}]]
},
"Submit Login via TLS Server [Webhook]": {
"main": [[{"node": "Login Successful? [Webhook]", "type": "main", "index": 0}]]
},
"Login Successful? [Webhook]": {
"main": [
[{"node": "Mark Login Success [Webhook]", "type": "main", "index": 0}],
[{"node": "Mark Login Failed [Webhook]", "type": "main", "index": 0}]
]
},
"Mark Login Success [Webhook]": {
"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]
},
"Mark Login Failed [Webhook]": {
"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]
}
},
"active": false,
"settings": { "executionOrder": "v1" }
}
Also available at
workflows/use-cases/cloudflare-challenge-account-login-scheduled-webhook.json.
Account Signup
Same two-phase pattern as login but for registering with your own email. The Config node includes nameField, emailField, passwordField and their corresponding values. The Prepare TLS Signup Request code builds a form POST with three fields instead of two.
Warning: The schedule trigger is named "Every 24 Hours (Use Carefully)" because repeated scheduled execution can create duplicate registrations. Leave the workflow inactive until you've configured and tested it.
Click to expand full workflow JSON
json
{
"name": "Cloudflare Challenge Account Signup โ CapSolver + Schedule + Webhook",
"nodes": [
{
"parameters": {
"rule": { "interval": [{ "field": "hours", "hoursInterval": 24 }] }
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [-640, 0],
"id": "cf777777-7777-7777-7777-777777777701",
"name": "Every 24 Hours (Use Carefully)"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "signup-001", "name": "targetURL", "value": "https://YOUR_CF_PROTECTED_SITE.com/signup", "type": "string" },
{ "id": "signup-002", "name": "signupActionURL", "value": "https://YOUR_CF_PROTECTED_SITE.com/signup", "type": "string" },
{ "id": "signup-003", "name": "proxy", "value": "YOUR_PROXY_HOST:PORT:USER:PASS", "type": "string" },
{ "id": "signup-004", "name": "nameField", "value": "name", "type": "string" },
{ "id": "signup-005", "name": "emailField", "value": "email", "type": "string" },
{ "id": "signup-006", "name": "passwordField", "value": "password", "type": "string" },
{ "id": "signup-007", "name": "nameValue", "value": "Your Name", "type": "string" },
{ "id": "signup-008", "name": "emailValue", "value": "[email protected]", "type": "string" },
{ "id": "signup-009", "name": "passwordValue", "value": "YOUR_ACCOUNT_PASSWORD", "type": "string" },
{ "id": "signup-010", "name": "successMarker", "value": "welcome", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-336, 0],
"id": "cf777777-7777-7777-7777-777777777702",
"name": "Set Signup Config [Schedule]"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.targetURL }}",
"proxy": "={{ $json.proxy }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [-32, 0],
"id": "cf777777-7777-7777-7777-777777777703",
"name": "Solve Cloudflare Challenge [Schedule]",
"credentials": { "capSolverApi": { "id": "YOUR_CREDENTIAL_ID", "name": "CapSolver account" } },
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const config = $('Set Signup Config [Schedule]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n if (!proxy) return '';\n if (proxy.startsWith('http')) return proxy;\n const parts = proxy.split(':');\n if (parts.length === 4) {\n return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n }\n return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n const solution = capResult.data.solution;\n const cookies = solution.cookies;\n cookieStr = cookies && typeof cookies === 'object'\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst params = new URLSearchParams();\nparams.set(config.nameField, config.nameValue);\nparams.set(config.emailField, config.emailValue);\nparams.set(config.passwordField, config.passwordValue);\n\nconst headers = {\n 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n 'accept-language': 'en-US,en;q=0.9',\n 'content-type': 'application/x-www-form-urlencoded',\n 'sec-ch-ua': secChUa,\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'sec-fetch-dest': 'document',\n 'sec-fetch-mode': 'navigate',\n 'sec-fetch-site': 'same-origin',\n 'sec-fetch-user': '?1',\n 'upgrade-insecure-requests': '1',\n 'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n url: config.signupActionURL,\n method: 'POST',\n proxy: toProxyURL(config.proxy),\n headers,\n body: params.toString()\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [272, 0],
"id": "cf777777-7777-7777-7777-777777777704",
"name": "Prepare TLS Signup Request [Schedule]"
},
{
"parameters": {
"method": "POST",
"url": "http://localhost:7878/fetch",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify($json) }}",
"options": { "timeout": 60000 }
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [576, 0],
"id": "cf777777-7777-7777-7777-777777777705",
"name": "Submit Signup via TLS Server [Schedule]"
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": false, "leftValue": "", "typeValidation": "strict", "version": 2 },
"conditions": [
{ "id": "signup-if-001", "leftValue": "={{ $json.status < 400 && String($json.body || '').includes($('Set Signup Config [Schedule]').item.json.successMarker) }}", "operator": { "type": "boolean", "operation": "true", "singleValue": true } }
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [880, 0],
"id": "cf777777-7777-7777-7777-777777777706",
"name": "Signup Successful? [Schedule]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "signup-012", "name": "action", "value": "account_signup", "type": "string" },
{ "id": "signup-013", "name": "status", "value": "success", "type": "string" },
{ "id": "signup-014", "name": "message", "value": "Configured account signup flow succeeded (via Cloudflare Challenge bypass)", "type": "string" },
{ "id": "signup-015", "name": "checkedAt", "value": "={{ new Date().toISOString() }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1184, -80],
"id": "cf777777-7777-7777-7777-777777777707",
"name": "Mark Signup Success [Schedule]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "signup-016", "name": "action", "value": "account_signup", "type": "string" },
{ "id": "signup-017", "name": "status", "value": "failed", "type": "string" },
{ "id": "signup-018", "name": "statusCode", "value": "={{ $json.status }}", "type": "number" },
{ "id": "signup-019", "name": "message", "value": "Signup response did not match the configured success marker", "type": "string" },
{ "id": "signup-020", "name": "checkedAt", "value": "={{ new Date().toISOString() }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1184, 120],
"id": "cf777777-7777-7777-7777-777777777708",
"name": "Mark Signup Failed [Schedule]"
},
{
"parameters": {
"httpMethod": "POST",
"path": "cloudflare-account-signup",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [-640, 420],
"id": "cf777777-7777-7777-7777-777777777709",
"name": "Webhook Trigger",
"webhookId": "cf777777-aaaa-bbbb-cccc-777777777709",
"onError": "continueRegularOutput"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "signup-021", "name": "targetURL", "value": "={{ $json.body.targetURL }}", "type": "string" },
{ "id": "signup-022", "name": "signupActionURL", "value": "={{ $json.body.signupActionURL }}", "type": "string" },
{ "id": "signup-023", "name": "proxy", "value": "={{ $json.body.proxy }}", "type": "string" },
{ "id": "signup-024", "name": "nameField", "value": "={{ $json.body.nameField }}", "type": "string" },
{ "id": "signup-025", "name": "emailField", "value": "={{ $json.body.emailField }}", "type": "string" },
{ "id": "signup-026", "name": "passwordField", "value": "={{ $json.body.passwordField }}", "type": "string" },
{ "id": "signup-027", "name": "nameValue", "value": "={{ $json.body.nameValue }}", "type": "string" },
{ "id": "signup-028", "name": "emailValue", "value": "={{ $json.body.emailValue }}", "type": "string" },
{ "id": "signup-029", "name": "passwordValue", "value": "={{ $json.body.passwordValue }}", "type": "string" },
{ "id": "signup-030", "name": "successMarker", "value": "={{ $json.body.successMarker }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-336, 420],
"id": "cf777777-7777-7777-7777-777777777710",
"name": "Set Signup Config [Webhook]"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.targetURL }}",
"proxy": "={{ $json.proxy }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [-32, 420],
"id": "cf777777-7777-7777-7777-777777777711",
"name": "Solve Cloudflare Challenge [Webhook]",
"credentials": { "capSolverApi": { "id": "YOUR_CREDENTIAL_ID", "name": "CapSolver account" } },
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const config = $('Set Signup Config [Webhook]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n if (!proxy) return '';\n if (proxy.startsWith('http')) return proxy;\n const parts = proxy.split(':');\n if (parts.length === 4) {\n return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n }\n return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n const solution = capResult.data.solution;\n const cookies = solution.cookies;\n cookieStr = cookies && typeof cookies === 'object'\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst params = new URLSearchParams();\nparams.set(config.nameField, config.nameValue);\nparams.set(config.emailField, config.emailValue);\nparams.set(config.passwordField, config.passwordValue);\n\nconst headers = {\n 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n 'accept-language': 'en-US,en;q=0.9',\n 'content-type': 'application/x-www-form-urlencoded',\n 'sec-ch-ua': secChUa,\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'sec-fetch-dest': 'document',\n 'sec-fetch-mode': 'navigate',\n 'sec-fetch-site': 'same-origin',\n 'sec-fetch-user': '?1',\n 'upgrade-insecure-requests': '1',\n 'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n url: config.signupActionURL,\n method: 'POST',\n proxy: toProxyURL(config.proxy),\n headers,\n body: params.toString()\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [272, 420],
"id": "cf777777-7777-7777-7777-777777777712",
"name": "Prepare TLS Signup Request [Webhook]"
},
{
"parameters": {
"method": "POST",
"url": "http://localhost:7878/fetch",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify($json) }}",
"options": { "timeout": 60000 }
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [576, 420],
"id": "cf777777-7777-7777-7777-777777777713",
"name": "Submit Signup via TLS Server [Webhook]"
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": false, "leftValue": "", "typeValidation": "strict", "version": 2 },
"conditions": [
{ "id": "signup-if-002", "leftValue": "={{ $json.status < 400 && String($json.body || '').includes($('Set Signup Config [Webhook]').item.json.successMarker) }}", "operator": { "type": "boolean", "operation": "true", "singleValue": true } }
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [880, 420],
"id": "cf777777-7777-7777-7777-777777777714",
"name": "Signup Successful? [Webhook]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "signup-032", "name": "action", "value": "account_signup", "type": "string" },
{ "id": "signup-033", "name": "status", "value": "success", "type": "string" },
{ "id": "signup-034", "name": "message", "value": "Configured account signup flow succeeded (via Cloudflare Challenge bypass)", "type": "string" },
{ "id": "signup-035", "name": "checkedAt", "value": "={{ new Date().toISOString() }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1184, 340],
"id": "cf777777-7777-7777-7777-777777777715",
"name": "Mark Signup Success [Webhook]"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "signup-036", "name": "action", "value": "account_signup", "type": "string" },
{ "id": "signup-037", "name": "status", "value": "failed", "type": "string" },
{ "id": "signup-038", "name": "statusCode", "value": "={{ $json.status }}", "type": "number" },
{ "id": "signup-039", "name": "message", "value": "Signup response did not match the configured success marker", "type": "string" },
{ "id": "signup-040", "name": "checkedAt", "value": "={{ new Date().toISOString() }}", "type": "string" }
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1184, 540],
"id": "cf777777-7777-7777-7777-777777777716",
"name": "Mark Signup Failed [Webhook]"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [1488, 420],
"id": "cf777777-7777-7777-7777-777777777717",
"name": "Respond to Webhook"
}
],
"connections": {
"Every 24 Hours (Use Carefully)": {
"main": [[{"node": "Set Signup Config [Schedule]", "type": "main", "index": 0}]]
},
"Set Signup Config [Schedule]": {
"main": [[{"node": "Solve Cloudflare Challenge [Schedule]", "type": "main", "index": 0}]]
},
"Solve Cloudflare Challenge [Schedule]": {
"main": [[{"node": "Prepare TLS Signup Request [Schedule]", "type": "main", "index": 0}]]
},
"Prepare TLS Signup Request [Schedule]": {
"main": [[{"node": "Submit Signup via TLS Server [Schedule]", "type": "main", "index": 0}]]
},
"Submit Signup via TLS Server [Schedule]": {
"main": [[{"node": "Signup Successful? [Schedule]", "type": "main", "index": 0}]]
},
"Signup Successful? [Schedule]": {
"main": [
[{"node": "Mark Signup Success [Schedule]", "type": "main", "index": 0}],
[{"node": "Mark Signup Failed [Schedule]", "type": "main", "index": 0}]
]
},
"Webhook Trigger": {
"main": [[{"node": "Set Signup Config [Webhook]", "type": "main", "index": 0}]]
},
"Set Signup Config [Webhook]": {
"main": [[{"node": "Solve Cloudflare Challenge [Webhook]", "type": "main", "index": 0}]]
},
"Solve Cloudflare Challenge [Webhook]": {
"main": [[{"node": "Prepare TLS Signup Request [Webhook]", "type": "main", "index": 0}]]
},
"Prepare TLS Signup Request [Webhook]": {
"main": [[{"node": "Submit Signup via TLS Server [Webhook]", "type": "main", "index": 0}]]
},
"Submit Signup via TLS Server [Webhook]": {
"main": [[{"node": "Signup Successful? [Webhook]", "type": "main", "index": 0}]]
},
"Signup Successful? [Webhook]": {
"main": [
[{"node": "Mark Signup Success [Webhook]", "type": "main", "index": 0}],
[{"node": "Mark Signup Failed [Webhook]", "type": "main", "index": 0}]
]
},
"Mark Signup Success [Webhook]": {
"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]
},
"Mark Signup Failed [Webhook]": {
"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]
}
},
"active": false,
"settings": { "executionOrder": "v1" }
}
Also available at
workflows/use-cases/cloudflare-challenge-account-signup-scheduled-webhook.json.
Challenge Solver API
The other use-case workflows solve the Cloudflare Challenge and fetch the page in one pipeline. But what if your application already has its own HTTP client with proper TLS fingerprinting โ and you just need the cf_clearance cookie and userAgent?
The Solver API workflow exposes a single webhook endpoint that accepts a website URL and proxy, solves the challenge via CapSolver, and returns the raw solution. No TLS server, no page fetch โ just the credentials your app needs to make its own request.
This follows the same minimal pattern as the other Solver APIs in the repo (reCAPTCHA v2 โ Solver API, reCAPTCHA v3 โ Solver API, Turnstile โ Solver API). The only difference is a Format Solution code node โ needed because AntiCloudflareTask returns cookies as an object that must be serialized, unlike reCAPTCHA/Turnstile which return a simple token string.
Flow:
Webhook (POST /solver-cloudflare-challenge) โ Cloudflare Challenge (CapSolver)
โ Format Solution โ Respond to Webhook
4 nodes, webhook-only, no schedule path, no TLS server dependency.
Request:
bash
curl -X POST https://your-n8n-instance.com/webhook/solver-cloudflare-challenge \
-H "Content-Type: application/json" \
-d '{
"websiteURL": "https://protected-site.com",
"proxy": "host:port:user:pass"
}'
Note: The field is
websiteURL(nottargetURL) โ matching the field name used by all other Solver APIs and the CapSolver node itself.
Successful response:
json
{
"success": true,
"cf_clearance": "abc123...",
"cookies": "cf_clearance=abc123...",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/145.0.0.0 ...",
"solvedAt": "2026-03-11T12:00:00.000Z"
}
Failed response (no challenge found, bad proxy, etc.):
json
{
"success": false,
"error": "No solution returned โ site may not be showing a challenge"
}
Why the Format Solution node is needed:
The other Solver APIs (reCAPTCHA v2, v3, Turnstile) can pass the CapSolver output directly to the Respond to Webhook node with $json.data โ because the solution is a simple object with a token string. Cloudflare Challenge is different:
solution.cookiesis an object ({ cf_clearance: "..." }), not a string โ it needs to be serialized to"cf_clearance=..."for use as aCookieheader- The workflow uses
continueOnFail: trueon the CapSolver node so it can return a structured error response instead of crashing - The response extracts just
cf_clearanceandcookies(the full cookie string) for convenience
Format Solution code:
javascript
const input = $input.first().json;
if (input.error || !input.data || !input.data.solution) {
const errorMsg = input.error
? (input.error.message || JSON.stringify(input.error))
: 'No solution returned โ site may not be showing a challenge';
return [{ json: { success: false, error: errorMsg } }];
}
const solution = input.data.solution;
const cookies = solution.cookies;
const cfClearance = (cookies && typeof cookies === 'object')
? (cookies.cf_clearance || '') : '';
const cookieString = (cookies && typeof cookies === 'object')
? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')
: (cookies || '');
return [{ json: {
success: true,
cf_clearance: cfClearance,
cookies: cookieString,
userAgent: solution.userAgent || '',
solvedAt: new Date().toISOString()
}}];
Key differences from the scraper workflow:
- No TLS server required โ the workflow doesn't fetch the page, so no Chrome TLS fingerprinting is needed on the n8n side
- No
N8N_BLOCK_ACCESS_TO_LOCALHOSTneeded โ no localhost HTTP calls - Caller is responsible for TLS fingerprinting โ if the caller's HTTP client has a non-browser TLS fingerprint, Cloudflare will still re-challenge even with a valid
cf_clearance - Webhook-only โ no schedule path (an API is called on demand, not on a timer)
- Uses
websiteURLโ matching the other Solver APIs and CapSolver's native field name (the scraper workflows usetargetURLbecause they also handle the fetch)
Click to expand full workflow JSON
json
{
"name": "Cloudflare Challenge โ Solver API",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "solver-cloudflare-challenge",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [-208, 0],
"id": "cf770001-7777-7777-7777-777777777701",
"name": "Webhook",
"webhookId": "cf770001-aaaa-bbbb-cccc-777777777701"
},
{
"parameters": {
"operation": "Cloudflare Challenge",
"type": "AntiCloudflareTask",
"websiteURL": "={{ $json.body.websiteURL }}",
"proxy": "={{ $json.body.proxy }}",
"userAgent": "={{ $json.body.userAgent }}",
"html": "={{ $json.body.html }}"
},
"type": "n8n-nodes-capsolver.capSolver",
"typeVersion": 1,
"position": [112, 0],
"id": "cf770001-7777-7777-7777-777777777702",
"name": "Cloudflare Challenge",
"credentials": {
"capSolverApi": {
"id": "YOUR_CREDENTIAL_ID",
"name": "CapSolver account"
}
},
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const input = $input.first().json;\n\nif (input.error || !input.data || !input.data.solution) {\n const errorMsg = input.error\n ? (input.error.message || JSON.stringify(input.error))\n : 'No solution returned โ site may not be showing a challenge';\n return [{ json: { success: false, error: errorMsg } }];\n}\n\nconst solution = input.data.solution;\nconst cookies = solution.cookies;\nconst cfClearance = (cookies && typeof cookies === 'object') ? (cookies.cf_clearance || '') : '';\nconst cookieString = (cookies && typeof cookies === 'object')\n ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n : (cookies || '');\n\nreturn [{ json: {\n success: true,\n cf_clearance: cfClearance,\n cookies: cookieString,\n userAgent: solution.userAgent || '',\n solvedAt: new Date().toISOString()\n}}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [400, 0],
"id": "cf770001-7777-7777-7777-777777777703",
"name": "Format Solution"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [700, 0],
"id": "cf770001-7777-7777-7777-777777777704",
"name": "Respond to Webhook"
}
],
"connections": {
"Webhook": {
"main": [[{ "node": "Cloudflare Challenge", "type": "main", "index": 0 }]]
},
"Cloudflare Challenge": {
"main": [[{ "node": "Format Solution", "type": "main", "index": 0 }]]
},
"Format Solution": {
"main": [[{ "node": "Respond to Webhook", "type": "main", "index": 0 }]]
}
},
"active": false,
"settings": { "executionOrder": "v1" }
}
Also available at
workflows/use-cases/cloudflare-challenge-solver-api.json.
Troubleshooting
CapSolver returns error 1002 ("Failed to solve")
| Symptom | CapSolver node fails with Solve failed: Failed to solve the captcha: 1002 |
| Cause | Datacenter proxy IP flagged by Cloudflare's bot scoring โ the solve attempt fails before completing |
| Fix | Switch to a residential or mobile proxy with sticky session support |
CapSolver times out after 120 seconds
| Symptom | Get task result timeout: unable to solve within 120000 seconds |
| Cause | Proxy is too slow, or the site uses Cloudflare's stricter UAM (Under Attack Mode) |
| Fix | Try a different proxy endpoint or a proxy provider with better routing to the target site's region |
"SSRF protection: Localhost access is blocked in strict mode"
| Symptom | Fetch via TLS Server node fails with SSRF protection error |
| Cause | N8N_BLOCK_ACCESS_TO_LOCALHOST is still set to its default value |
| Fix | Run: N8N_BLOCK_ACCESS_TO_LOCALHOST=false pm2 restart n8n --update-env โ the --update-env flag is required |
Got a challenge page back instead of content (status 403)
| Symptom | HTTP 200 from workflow, but the response body is a Cloudflare challenge page |
| Cause 1 | The target site redirected to a subdomain โ cf_clearance is domain-specific and doesn't transfer |
| Cause 2 | The site has additional bot protection on top of Cloudflare (proprietary layer) |
| Fix | Confirm the exact URL that serves the content. If it's a different domain or subdomain, you need to solve the challenge for that specific URL |
TLS server returns {"error": "url is required"} or empty body
| Symptom | Fetch via TLS Server receives {"": ""} as the request body |
| Cause | HTTP Request node using contentType: "json" with JSON.stringify() โ n8n serializes incorrectly |
| Fix | Set contentType to raw and rawContentType to application/json as shown in the node configuration above |
TLS server not responding
| Symptom | Connection refused on port 7878 |
| Cause | The Go binary is not running |
| Fix | Run pm2 status โ if tls-server is not listed or shows stopped, run pm2 start ~/tls-server/main --name tls-server && pm2 save |
"ERROR_KEY_DOES_NOT_EXIST" or "ERROR_ZERO_BALANCE"
| Symptom | CapSolver node fails immediately with an API error |
| Cause | Invalid API key or insufficient account balance |
| Fix | Check your API key in Settings โ Credentials and verify your balance at the CapSolver Dashboard |
Best Practices
-
Use residential or mobile proxies โ Datacenter IPs are flagged by Cloudflare's bot scoring. For
AntiCloudflareTaskto succeed on most protected sites, you need a proxy with residential or mobile IP ranges. -
Use the same proxy IP for solve and fetch โ
cf_clearanceis bound to the IP address that solved the challenge. If your proxy rotates IPs between requests, Cloudflare will re-challenge. Use sticky session mode on your proxy provider. -
Forward
userAgentexactly โ CapSolver selects a specific User-Agent for each solve. Thecf_clearancecookie is validated against this UA. Any change โ even a minor version difference โ can cause Cloudflare to reject the clearance. -
Fetch the page immediately after solving โ
cf_clearancecan expire within minutes on strictly-configured sites. The workflow is designed to chain these steps, but avoid adding delays between the CapSolver node and the TLS server fetch. -
Keep the TLS server alive with PM2 โ Run
pm2 saveandpm2 startupto ensure the TLS server restarts automatically after reboots. -
Use self-hosted n8n โ The TLS server must run on the same machine as n8n. n8n Cloud does not support running local servers alongside workflows.
Conclusion
You've built a complete Cloudflare Challenge bypass pipeline in n8n โ no browser automation, no Puppeteer, no Playwright. Just three components working together: CapSolver to solve the challenge, a Go TLS server to spoof Chrome's network fingerprint, and an n8n workflow to orchestrate everything.
The key insight is that solving the challenge is only half the problem. Without matching TLS fingerprints on the subsequent fetch, cf_clearance is useless โ Cloudflare inspects the handshake, not just the cookie. The httpcloak TLS server handles that layer, making the fetch indistinguishable from a real Chrome browser at the network level.
The repo now gives you actual starting templates for Cloudflare-protected sites:
- Basic scraping โ retrieve raw page content
- Site health monitoring โ track uptime with consecutive-failure alerting
- Price monitoring โ extract and compare prices over time
- Account login โ log into your own account behind Cloudflare
- Account signup โ register with your own email behind Cloudflare
- Challenge Solver API โ expose a webhook that returns raw
cf_clearance+userAgentfor external apps
All of them use the same two-tier pattern: CapSolver solves the challenge, the TLS server makes the actual request. The Solver API is the exception โ it skips the TLS server entirely and returns just the solution, letting your own application handle the fetch. Configure the placeholders, keep the workflows inactive until they match your target, then activate.
Ready to get started? Sign up for CapSolver and use bonus code n8n for an extra 8% bonus on your first recharge!

Frequently Asked Questions
What is AntiCloudflareTask, and how is it different from AntiTurnstileTaskProxyLess?
AntiCloudflareTask solves the full-page Cloudflare Bot Management challenge โ the "Just a momentโฆ" screen that blocks site access entirely. It requires a proxy because CapSolver must load the actual protected page through a browser. AntiTurnstileTaskProxyLess solves Turnstile widgets embedded inside pages (login forms, signup forms) and does not require a proxy. Different challenges, different task types.
Why can't I just use n8n's built-in HTTP Request node to fetch the page?
n8n's HTTP Request node uses Go's standard net/http library, which has a distinct TLS fingerprint that Cloudflare detects. Even with a valid cf_clearance cookie, Cloudflare will re-challenge any request that doesn't match a known browser TLS profile. The TLS server solves this by using httpcloak to spoof a real Chrome TLS stack.
Why does my datacenter proxy keep failing?
Cloudflare's bot scoring assigns risk scores to IP addresses. Datacenter IPs (from AWS, GCP, VPS providers, etc.) are well-known and get high risk scores. AntiCloudflareTask uses your proxy to load the challenge page, and if Cloudflare detects the IP as a datacenter, it either serves a harder challenge that CapSolver can't solve, or fails the challenge entirely. Residential and mobile IPs have lower risk scores and pass more reliably.
Does cf_clearance expire?
Yes. The expiration depends on the site's Cloudflare configuration โ it can range from a few minutes to 24 hours. For recurring scraping jobs, the scheduled workflow (every 6 hours) re-solves the challenge regularly. For on-demand scraping, the webhook path solves a fresh challenge on every request.
What if the site uses both Turnstile and the Cloudflare Challenge?
Some sites have Cloudflare Bot Management active at the domain level (which shows the challenge page before you can access anything) and also embed a Turnstile widget inside a specific form. In this case, you need to handle them separately: use this workflow to bypass the initial Cloudflare Challenge and retrieve the page, then use a Turnstile solver for the form submission. Each is a distinct step.
Can I use this with n8n Cloud?
Not easily. The TLS server is a local Go binary that must run on the same machine as n8n so the workflow can call http://localhost:7878/fetch. n8n Cloud does not allow running local services alongside workflows. You would need a self-hosted n8n instance, or run the TLS server as an externally accessible service and update the URL in the workflow โ though that adds latency and infrastructure overhead.
What sites does this work on?
This approach works on sites protected by Cloudflare Bot Management that show the standard "Verifying your browserโฆ" interstitial. It may not work on sites that use Cloudflare's stricter UAM (Under Attack Mode) or that combine Cloudflare with additional proprietary bot protection layers. Success also depends on proxy quality โ a residential proxy that works well for one site may not work for another depending on regional Cloudflare rules.
Compliance Disclaimer: The information provided on this blog is for informational purposes only. CapSolver is committed to compliance with all applicable laws and regulations. The use of the CapSolver network for illegal, fraudulent, or abusive activities is strictly prohibited and will be investigated. Our captcha-solving solutions enhance user experience while ensuring 100% compliance in helping solve captcha difficulties during public data crawling. We encourage responsible use of our services. For more information, please visit our Terms of Service and Privacy Policy.
More

How to Solve Cloudflare Challenge in n8n with CapSolver
Build a working Cloudflare Challenge scraper in n8n using CapSolver and a ChromeโTLS Go server to bypass bot protection.

Ethan Collins
12-Mar-2026

How to Solve reCAPTCHA v2/v3 Using CapSolver and n8n
Build a eCAPTCHA v2/v3 solver API using CapSolver and n8n. Learn how to automate token solving, submit it to websites, and extract protected data with no coding.

Lucas Mitchell
10-Mar-2026

How to Solve Cloudflare Turnstile Using CapSolver and n8n
Build a Cloudflare Turnstile solver API using CapSolver and n8n. Learn how to automate token solving, submit it to websites, and extract protected data with no coding.

Ethan Collins
10-Mar-2026

Browser Automation for Developers: Mastering Selenium & CAPTCHA in 2026
Master browser automation for developers with this 2026 guide. Learn Selenium WebDriver Java, Actions Interface, and how to solve CAPTCHA using CapSolver.

Adรฉlia Cruz
02-Mar-2026

PicoClaw Automation: A Guide to Integrating CapSolver API
Learn to integrate CapSolver with PicoClaw for automated CAPTCHA solving on ultra-lightweight $10 edge hardware.

Ethan Collins
26-Feb-2026

How to Solve Captcha in Nanobot with CapSolver
Automate CAPTCHA solving with Nanobot and CapSolver. Use Playwright to solve reCAPTCHA and Cloudflare autonomously.

Ethan Collins
26-Feb-2026

