Solving Cloudflare Challenges
Bypass Cloudflare Turnstile and JS challenges to access protected pages — using the Browserless /unblock endpoint or BQL's solve(type: cloudflare) mutation.
- A Browserless API token from your account dashboard
Steps
- REST API
- Frameworks
- BQL
Use the /unblock REST endpoint to bypass Cloudflare challenges and retrieve page content. No WebSocket connection needed.
- cURL
- JavaScript
- Python
- Java
- C#
- Go
- PHP
- Ruby
1. Build the request
Use the /unblock endpoint with proxy=residential — Cloudflare's bot detection is significantly harder to bypass from datacenter IPs, so residential proxies have a much higher success rate:
https://production-sfo.browserless.io/unblock?token=YOUR_API_TOKEN_HERE&proxy=residential
2. Send the request
curl -X POST \
"https://production-sfo.browserless.io/unblock?token=YOUR_API_TOKEN_HERE&proxy=residential" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example-cloudflare-protected.com",
"content": true,
"cookies": false,
"screenshot": false,
"browserWSEndpoint": false
}'
3. Check the output
The response contains the fully unblocked page HTML:
{
"content": "<!DOCTYPE html><html>...</html>",
"cookies": [],
"screenshot": null,
"browserWSEndpoint": null
}
Set "screenshot": true or "cookies": true to include those in the response as well.
1. Send the request
const response = await fetch(
'https://production-sfo.browserless.io/unblock?token=YOUR_API_TOKEN_HERE&proxy=residential',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://example-cloudflare-protected.com',
content: true,
cookies: false,
screenshot: false,
browserWSEndpoint: false,
}),
}
);
const { content } = await response.json();
console.log(content.slice(0, 500));
2. Check the output
Run with node unblock.mjs. The content field contains the fully rendered page HTML.
1. Install dependencies
pip install requests
2. Send the request
import requests
response = requests.post(
'https://production-sfo.browserless.io/unblock?token=YOUR_API_TOKEN_HERE&proxy=residential',
json={
'url': 'https://example-cloudflare-protected.com',
'content': True,
'cookies': False,
'screenshot': False,
'browserWSEndpoint': False,
},
)
data = response.json()
print(data['content'][:500])
3. Check the output
Run with python unblock.py. The content field contains the fully rendered page HTML.
1. Install dependencies
Add Gson to your pom.xml for JSON parsing (java.net.http.HttpClient ships with the JDK):
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
2. Send the request
import com.google.gson.JsonParser;
import java.net.URI;
import java.net.http.*;
public class CloudflareUnblock {
public static void main(String[] args) throws Exception {
String token = "YOUR_API_TOKEN_HERE";
HttpClient client = HttpClient.newHttpClient();
String body = "{\"url\":\"https://example-cloudflare-protected.com\","
+ "\"content\":true,\"cookies\":false,\"screenshot\":false,\"browserWSEndpoint\":false}";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://production-sfo.browserless.io/unblock?token=" + token + "&proxy=residential"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
String content = JsonParser.parseString(res.body()).getAsJsonObject()
.get("content").getAsString();
System.out.println(content.substring(0, Math.min(500, content.length())));
}
}
3. Check the output
Compile and run with mvn exec:java. The content field contains the fully rendered page HTML.
1. Dependencies
System.Net.Http.HttpClient and System.Text.Json are part of the .NET standard library.
2. Send the request
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class CloudflareUnblock
{
static async Task Main()
{
const string token = "YOUR_API_TOKEN_HERE";
using var client = new HttpClient();
var body = new StringContent(
"{\"url\":\"https://example-cloudflare-protected.com\","
+ "\"content\":true,\"cookies\":false,\"screenshot\":false,\"browserWSEndpoint\":false}",
Encoding.UTF8, "application/json");
var res = await client.PostAsync(
$"https://production-sfo.browserless.io/unblock?token={token}&proxy=residential",
body);
using var json = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
string content = json.RootElement.GetProperty("content").GetString();
Console.WriteLine(content[..Math.Min(500, content.Length)]);
}
}
3. Check the output
Run with dotnet run. The content field contains the fully rendered page HTML.
1. Dependencies
encoding/json and net/http are part of Go's standard library.
2. Send the request
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
type UnblockResponse struct {
Content string `json:"content"`
}
func main() {
token := "YOUR_API_TOKEN_HERE"
body := `{"url":"https://example-cloudflare-protected.com","content":true,"cookies":false,"screenshot":false,"browserWSEndpoint":false}`
req, _ := http.NewRequest("POST",
"https://production-sfo.browserless.io/unblock?token="+token+"&proxy=residential",
bytes.NewBufferString(body),
)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
var result UnblockResponse
json.Unmarshal(data, &result)
end := len(result.Content)
if end > 500 { end = 500 }
fmt.Println(result.Content[:end])
}
3. Check the output
Run with go run main.go. The content field contains the fully rendered page HTML.
1. Dependencies
This example uses PHP's built-in curl. No Composer packages needed.
2. Send the request
<?php
$token = 'YOUR_API_TOKEN_HERE';
$ch = curl_init("https://production-sfo.browserless.io/unblock?token={$token}&proxy=residential");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://example-cloudflare-protected.com',
'content' => true,
'cookies' => false,
'screenshot' => false,
'browserWSEndpoint' => false,
]),
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
curl_close($ch);
$data = json_decode($body, true);
echo substr($data['content'], 0, 500) . "\n";
3. Check the output
Run with php unblock.php. The content field contains the fully rendered page HTML.
1. Dependencies
net/http and json are part of Ruby's standard library.
2. Send the request
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_API_TOKEN_HERE'
uri = URI("https://production-sfo.browserless.io/unblock?token=#{TOKEN}&proxy=residential")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request.body = JSON.generate({
url: 'https://example-cloudflare-protected.com',
content: true,
cookies: false,
screenshot: false,
browserWSEndpoint: false,
})
response = http.request(request)
data = JSON.parse(response.body)
puts data['content'][0, 500]
3. Check the output
Run with ruby unblock.rb. The content field contains the fully rendered page HTML.
Use a browser connection — request a browserWSEndpoint from /unblock, then connect to the already-unblocked session. The ttl parameter keeps the browser alive long enough for your client to connect.
- Puppeteer
- Playwright
1. Install dependencies
npm install puppeteer-core
2. Unblock and connect
Request a browserWSEndpoint from /unblock, then connect Puppeteer to the already-unblocked session:
import puppeteer from 'puppeteer-core';
const TOKEN = 'YOUR_API_TOKEN_HERE';
const { browserWSEndpoint } = await fetch(
`https://production-sfo.browserless.io/unblock?token=${TOKEN}&proxy=residential`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://example-cloudflare-protected.com',
browserWSEndpoint: true,
ttl: 30000,
}),
}
).then((r) => r.json());
// The /unblock response returns a raw WebSocket URL — append the token before connecting.
const browser = await puppeteer.connect({
browserWSEndpoint: `${browserWSEndpoint}?token=${TOKEN}`,
});
try {
const [page] = await browser.pages();
console.log('Title:', await page.title());
console.log('URL:', page.url());
} finally {
// Always close to release the session even on error.
await browser.close();
}
3. Check the output
Run with node unblock.mjs. The browser is already past the Cloudflare check when Puppeteer connects.
- JavaScript
- Python
- Java
- C#
1. Install dependencies
npm install playwright-core
2. Unblock and connect
import { chromium } from 'playwright-core';
const TOKEN = 'YOUR_API_TOKEN_HERE';
const { browserWSEndpoint } = await fetch(
`https://production-sfo.browserless.io/unblock?token=${TOKEN}&proxy=residential`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://example-cloudflare-protected.com',
browserWSEndpoint: true,
ttl: 30000,
}),
}
).then((r) => r.json());
// The /unblock response returns a raw WebSocket URL — append the token before connecting.
const browser = await chromium.connectOverCDP(`${browserWSEndpoint}?token=${TOKEN}`);
try {
const context = browser.contexts()[0];
const page = context.pages()[0];
console.log('Title:', await page.title());
console.log('URL:', page.url());
} finally {
// Always close to release the session even on error.
await browser.close();
}
3. Check the output
Run with node unblock.mjs. The browser is already past the Cloudflare check when Playwright connects.
1. Install dependencies
pip install requests playwright
2. Unblock and connect
import requests
from playwright.sync_api import sync_playwright
TOKEN = 'YOUR_API_TOKEN_HERE'
res = requests.post(
f'https://production-sfo.browserless.io/unblock?token={TOKEN}&proxy=residential',
json={
'url': 'https://example-cloudflare-protected.com',
'browserWSEndpoint': True,
'ttl': 30000,
},
)
# The /unblock response returns a raw WebSocket URL — append the token before connecting.
ws_endpoint = res.json()['browserWSEndpoint']
with sync_playwright() as p:
# connect_over_cdp exposes the pre-existing browser context with the already-unblocked page.
browser = p.chromium.connect_over_cdp(f"{ws_endpoint}?token={TOKEN}")
try:
context = browser.contexts[0]
page = context.pages[0]
print('Title:', page.title())
print('URL:', page.url)
finally:
# Always close to release the session even on error.
browser.close()
3. Check the output
Run with python unblock.py. The browser is already past the Cloudflare check when Playwright connects.
1. Install dependencies
Add Playwright and Gson to your pom.xml:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.44.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
2. Unblock and connect
import com.google.gson.JsonParser;
import com.microsoft.playwright.*;
import java.net.URI;
import java.net.http.*;
public class CloudflareUnblock {
public static void main(String[] args) throws Exception {
String TOKEN = "YOUR_API_TOKEN_HERE";
HttpClient client = HttpClient.newHttpClient();
String body = "{\"url\":\"https://example-cloudflare-protected.com\","
+ "\"browserWSEndpoint\":true,\"ttl\":30000}";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://production-sfo.browserless.io/unblock?token=" + TOKEN + "&proxy=residential"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> httpRes = client.send(req, HttpResponse.BodyHandlers.ofString());
// The /unblock response returns a raw WebSocket URL — append the token before connecting.
String wsEndpoint = JsonParser.parseString(httpRes.body()).getAsJsonObject()
.get("browserWSEndpoint").getAsString() + "?token=" + TOKEN;
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().connectOverCDP(wsEndpoint);
try {
BrowserContext context = browser.contexts().get(0);
Page page = context.pages().get(0);
System.out.println("Title: " + page.title());
System.out.println("URL: " + page.url());
} finally {
// Always close to release the session even on error.
browser.close();
}
}
}
}
3. Check the output
Compile and run with mvn exec:java. The browser is already past the Cloudflare check when Playwright connects.
1. Install dependencies
dotnet add package Microsoft.Playwright
2. Unblock and connect
using Microsoft.Playwright;
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class CloudflareUnblock
{
static async Task Main()
{
const string TOKEN = "YOUR_API_TOKEN_HERE";
using var httpClient = new HttpClient();
var body = new StringContent(
"{\"url\":\"https://example-cloudflare-protected.com\",\"browserWSEndpoint\":true,\"ttl\":30000}",
Encoding.UTF8, "application/json");
var res = await httpClient.PostAsync(
$"https://production-sfo.browserless.io/unblock?token={TOKEN}&proxy=residential",
body);
using var json = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
// The /unblock response returns a raw WebSocket URL — append the token before connecting.
string wsEndpoint = json.RootElement.GetProperty("browserWSEndpoint").GetString()
+ $"?token={TOKEN}";
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectOverCDPAsync(wsEndpoint);
try
{
var context = browser.Contexts[0];
var page = context.Pages[0];
Console.WriteLine($"Title: {await page.TitleAsync()}");
Console.WriteLine($"URL: {page.Url}");
}
finally
{
// Always close to release the session even on error.
await browser.CloseAsync();
}
}
}
3. Check the output
Run with dotnet run. The browser is already past the Cloudflare check when Playwright connects.
1. Write the mutation
Use the stealth BQL endpoint and chain goto with solve(type: cloudflare):
mutation UnblockCloudflare {
goto(url: "https://example-cloudflare-protected.com", waitUntil: networkIdle) {
status
}
solve(type: cloudflare) {
found
solved
time
}
title {
title
}
}
Send this to the /stealth/bql endpoint:
POST https://production-sfo.browserless.io/stealth/bql?token=YOUR_API_TOKEN_HERE
2. Run it
Paste into the BQL IDE (switch to the stealth browser), or send via HTTP (see the cURL tab).
3. Check the output
{
"data": {
"goto": { "status": 200 },
"solve": { "found": true, "solved": true, "time": 12340 },
"title": { "title": "Welcome to Example Site" }
}
}
Next steps
- Solving reCAPTCHAs — solve CAPTCHA challenges on non-Cloudflare sites
- Browse Cloudflare Access-Protected Pages — authenticate through Cloudflare Access zero-trust policies
- Scrape Structured Data — extract data once you're past the challenge