CAPSOLVER
Blog
How to Solve Cloudflare Challenge in n8n with CapSolver

How to Solve Cloudflare Challenge in n8n with CapSolver

Logo of 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 scraping, account login, and a standalone challenge solver API — 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.


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.

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 SettingsCredentials.

n8n credentials page showing CapSolver account

Step 2: Create the CapSolver Credential

  1. Click Create credential (top right)
  2. Search for "CapSolver" and select CapSolver API
  3. Enter your API Key from the CapSolver Dashboard
  4. Leave Allowed HTTP Request Domains set to All
  5. Click Save

You should see a green "Connection tested successfully" banner.

CapSolver credential configuration with successful connection test

Important: Every CapSolver node in your workflows will reference this credential. You only need to create it once — all your solver workflows will share the same credential.


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 Copy
mkdir -p ~/tls-server && cd ~/tls-server
go Copy
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 Copy
go mod init tls-server
go get github.com/sardanioss/httpcloak/client
go build -o main main.go

Run the server

bash Copy
./main

Verify it's running (in a new terminal)

bash Copy
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 N8N_BLOCK_ACCESS_TO_LOCALHOST=false environment variable and restart your n8n instance. How you do this depends on how you run n8n:

If you run n8n directly:

bash Copy
export N8N_BLOCK_ACCESS_TO_LOCALHOST=false
n8n start

If you use Docker:

Add -e N8N_BLOCK_ACCESS_TO_LOCALHOST=false to your docker run command, or add it to the environment section in your docker-compose.yml.


Workflow: Cloudflare Challenge Solver API

This workflow creates a POST endpoint that accepts a Cloudflare-protected URL and proxy, solves the challenge via CapSolver, and returns the raw cf_clearance cookie and userAgent. No TLS server needed — your application handles the fetch.

How It Works

Copy
Webhook (POST /solver-cloudflare-challenge) → Cloudflare Challenge (CapSolver)
                                             → Format Solution → Respond to Webhook

4 nodes, webhook-only, no schedule path, no TLS server dependency.

  1. Webhook — Receives POST requests with websiteURL and proxy
  2. Cloudflare Challenge — CapSolver solves the challenge using AntiCloudflareTask
  3. Format Solution — Serializes cookies object to a cookie string, handles errors via continueOnFail
  4. Respond to Webhook — Returns cf_clearance, serialized cookie string, and userAgent

Node Configuration

1. Webhook Node

Setting Value
HTTP Method POST
Path solver-cloudflare-challenge
Respond Response Node

This creates an endpoint at: https://your-n8n-instance.com/webhook/solver-cloudflare-challenge

2. CapSolver Node (Cloudflare Challenge)

Parameter Value Description
Operation Cloudflare Challenge Selects AntiCloudflareTask
Type AntiCloudflareTask Full-page Cloudflare Challenge
Website URL ={{ $json.body.websiteURL }} The Cloudflare-protected URL
Proxy ={{ $json.body.proxy }} Residential proxy in host:port:user:pass format
Continue On Fail true Returns structured errors instead of crashing

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 cookie
  • userAgent — the exact User-Agent string the browser used during the solve

Note: The field is websiteURL (not targetURL) — matching the field name used by all other Solver APIs and the CapSolver node itself.

3. Format Solution (Code Node)

This node is needed because AntiCloudflareTask returns cookies as an object ({ cf_clearance: "..." }), not a simple token string like reCAPTCHA or Turnstile. It serializes the cookie, extracts cf_clearance, and returns a structured error if CapSolver failed.

javascript Copy
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()
}}];

4. Respond to Webhook

Setting Value
Respond With JSON
Response Body ={{ JSON.stringify($json) }}

Test It

bash Copy
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"
  }'

Successful response:

json Copy
{
  "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 Copy
{
  "success": false,
  "error": "No solution returned — site may not be showing a challenge"
}

Import This Workflow

Copy the JSON below and import it into n8n via Menu → Import from JSON. After importing, select your CapSolver credential in the Cloudflare Challenge node.

Click to expand workflow JSON
json Copy
{
  "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" }
}

Workflow: Using Cloudflare Challenge Solutions

So far, the Solver API above shows how to get a solved cf_clearance cookie and userAgent. But what do you actually do with them?

Unlike reCAPTCHA or Turnstile where you submit a token in a form field, the Cloudflare Challenge returns a cookie (cf_clearance) that must be sent as a header on every subsequent request. The cookie is bound to the proxy IP and User-Agent used during the solve — both must match exactly on the fetch.

Here's the general pattern:

  1. Solve the Cloudflare Challenge → Get the cf_clearance cookie and userAgent from CapSolver
  2. Prepare the request → Build headers with the cookie, matching User-Agent, and Chrome-like sec-ch-ua headers
  3. Fetch through the TLS server → Send the request via http://localhost:7878/fetch to match Chrome's TLS fingerprint
  4. Verify the response → Check if the site returned real content (status 200 with HTML) vs. a re-challenge page
  5. Process the result → Extract the data you need using the HTML node, Edit Fields, or Code node

Key concept: Standard HTTP clients fail here even with a valid cf_clearance cookie — Cloudflare fingerprints the TLS handshake itself. The Go TLS server (httpcloak) makes the fetch look exactly like Chrome at the network level. Every workflow that fetches a Cloudflare-protected page must go through the TLS server.

Example: Cloudflare Challenge Scraper

Cloudflare Challenge workflow in n8n with schedule and webhook paths

Workflow Flow

Schedule path:

Copy
Every 6 Hours → Set Target Config [Schedule] → Solve Cloudflare Challenge
             → Prepare TLS Request → Fetch via TLS Server → Extract Result

Webhook path:

Copy
Webhook Trigger → Set Target Config [Webhook] → Solve Cloudflare Challenge
               → Prepare TLS Request → Fetch via TLS Server → Extract Result → Respond to Webhook

How It Works

  1. Set Target Config — Stores targetURL and proxy (in host:port:user:pass format). The Schedule path uses hardcoded values; the Webhook path reads them from the POST body.
  2. Solve Cloudflare Challenge — CapSolver node with onError: "continueRegularOutput" — continues even if the page isn't currently showing a challenge.
  3. Prepare TLS Request — Code node that converts host:port:user:pass proxy to http://user:pass@host:port URL format, serializes solution.cookies to a cookie header string, and builds Chrome-like request headers with the exact userAgent from the solve.
  4. Fetch via TLS Server — HTTP Request to http://localhost:7878/fetch using contentType: "raw" (not "json" — n8n's JSON mode corrupts the body).
  5. Extract Result — Pulls status, body, and fetchedAt from the TLS server response.
  6. Respond to Webhook — Returns the result as JSON (webhook path only).

Why contentType: "raw" and not "json"? n8n's json content type mode expects body parameters as key-value pairs. If you pass JSON.stringify($json) as a string, n8n treats the whole string as a single malformed param and sends {"": ""} to the server. Using raw mode sends the body exactly as the expression evaluates.

Click to expand workflow JSON
json Copy
{
  "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"
  }
}

Workflow: Use-Case Examples

The Solver API and scraper example above show the core pattern: solve the challenge, use the solution to fetch through TLS. The following workflows extend this pattern to production-ready use cases — each with dual triggers (schedule + webhook), persistent state tracking, and structured output. Each requires the same prerequisites: a self-hosted n8n instance, the TLS server running on port 7878, a residential proxy, and a CapSolver credential.

Workflow Purpose
Cloudflare Challenge Scraping — Price & Product Details — 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

Example 1: Scraping — Price & Product Details

This workflow scrapes a product page every 6 hours (schedule) or on demand (webhook), extracts the price using the HTML node, and compares it against the previously stored value.

Schedule path:

Copy
Every 6 Hours → Set Target Config → Solve CF Challenge → Prepare TLS Request
             → Fetch via TLS Server → Extract Data → Compare Data
             → Data Changed? → Build Alert / No Change

Error handling: If CapSolver fails, the workflow continues without cookies (via continueOnFail). The TLS server fetch may still succeed if the page isn't currently showing a challenge.

Key behaviors:

  • Uses dataPropertyName: "body" (not "data") because the TLS server returns { status, body, headers }
  • HTML node extracts price and product name via CSS selectors (.product-price, h1)
  • $workflow.staticData.lastPrice persists the previous price across executions
  • Price comparison detects both drops (severity: deal) and increases (severity: info)
  • Proxy format auto-conversion: host:port:user:passhttp://user:pass@host:port via toProxyURL() helper
Click to expand full workflow JSON
json Copy
{
  "name": "Cloudflare Challenge Scraping — Price & Product Details — 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 Data"
    },
    {
      "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 Data"
    },
    {
      "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": "Data 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 Data [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 Data [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": "Data 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 Data", "type": "main", "index": 0}]]
    },
    "Extract Data": {
      "main": [[{"node": "Compare Data", "type": "main", "index": 0}]]
    },
    "Compare Data": {
      "main": [[{"node": "Data Changed?", "type": "main", "index": 0}]]
    },
    "Data 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 Data [Webhook]", "type": "main", "index": 0}]]
    },
    "Extract Data [Webhook]": {
      "main": [[{"node": "Compare Data [Webhook]", "type": "main", "index": 0}]]
    },
    "Compare Data [Webhook]": {
      "main": [[{"node": "Data Changed? [Webhook]", "type": "main", "index": 0}]]
    },
    "Data 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" }
}

Example 2: Account Login

This workflow automates login to a Cloudflare-protected site. A Set Login Config node centralizes all parameters — [Schedule] for the schedule path and [Webhook] for the on-demand webhook path.

Schedule path:

Copy
Every 24 Hours → Set Login Config → Solve CF Challenge
             → Prepare TLS Login Request → Submit Login via TLS Server
             → Login Successful? → Mark Success / Mark Failed

Error handling: If CapSolver fails, the workflow continues without cookies (via continueOnFail). The login request will likely fail, which the Login Successful? node catches.

Key behaviors:

  • Uses cf_clearance cookie + userAgent as HTTP headers (no token in form body — unlike reCAPTCHA login which submits g-recaptcha-response)
  • Form fields via URLSearchParams — edit field names (usernameField, passwordField) in Set Login Config to match your site
  • Login Successful? checks status < 400 AND successMarker in response body
  • Webhook path returns result as JSON via Respond to Webhook
Click to expand full workflow JSON
json Copy
{
  "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" }
}

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:

  • Challenge Solver API — expose a webhook that returns raw cf_clearance + userAgent for external apps
  • Scraping — extract price and product data from Cloudflare-protected pages, with change detection
  • Account login — log into your own account behind Cloudflare

The Solver API is the simplest entry point — 4 nodes, no TLS server needed. For workflows that fetch pages directly, CapSolver solves the challenge and the TLS server makes the actual request. Configure the placeholders, keep the workflows inactive until they match your target, then activate.

Tip: These workflows use Schedule + Webhook triggers, but you can swap the trigger node to any n8n trigger — manual, app event, form submission, etc. After fetching data, use n8n's built-in nodes to save results to Google Sheets, databases, cloud storage, or send alerts via Telegram/Slack/Email.


Ready to get started? Sign up for CapSolver and use bonus code n8n for an extra 8% bonus on your first recharge!

CapSolver bonus code banner

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.

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