How to Solve Cloudflare Turnstile in LangGraph Agents

Ethan Collins
Pattern Recognition Specialist
23-Jul-2026
TL;DR
- A LangGraph Cloudflare Turnstile solver integration should model CAPTCHA handling as a controlled recovery node, not an unrestricted tool available on every route.
- CapSolver documents
capsolver-agentas the tool-adapter layer overcapsolver-core, with LangChain tools for agent frameworks and browser methods for Playwright sessions. - Keep navigation decisions in the graph, deterministic authorization in application code, and recognition in the CapSolver service.
- Preserve one browser session through detection, solving, fill-back, and final page assertion; never return the raw token to the model.
- Use bounded retries, explicit terminal states, redacted tracing, and human review for state-changing or unclear actions.
- The example graph below includes state definitions, policy routing, a CapSolver recovery node, failure handling, and verification.
What a LangGraph Cloudflare Turnstile integration should do
A LangGraph Cloudflare Turnstile integration lets an agent recover from a verification step inside an authorized browser workflow and then resume the original task. The graph should not ask the language model to click or reason through the widget. Instead, the model or browser controller detects that the workflow is blocked, the graph evaluates policy, and a deterministic adapter calls the documented CapSolver capability.
CapSolver documents this division of labor in its agent tools guide: the model handles navigation and decisions, capsolver-agent exposes tool schemas and an executor, and capsolver-core performs detection, solving, and browser fill-back.
This architecture gives LangGraph a useful role. It can make recovery observable, enforce retry budgets, route sensitive actions to a human, and ensure the browser verifies success before the graph continues.
Prerequisites and supported integration path
Use an isolated Python environment. CapSolver's current official guide installs the core and agent packages from GitHub:
bash
python -m venv .venv
source .venv/bin/activate
pip install git+https://github.com/capsolver-ai/capsolver-core.git
pip install "capsolver-agent[langchain] @ git+https://github.com/capsolver-ai/capsolver-agent.git"
pip install langchain-openai langgraph playwright
playwright install chromium
Place CAPSOLVER_API_KEY and any model credential in an approved secret store. Do not write real values into graph state, checkpoints, prompts, tracing events, or source files.
You also need:
- an owned or explicitly authorized target;
- a domain allowlist;
- a defined business purpose;
- a browser session registry;
- a maximum retry count;
- a timeout budget;
- a final page assertion;
- a human-review rule for consequential actions.
Design the LangGraph state
Keep only non-secret operational data in the graph state:
python
from typing import Literal, TypedDict
class AgentState(TypedDict, total=False):
request_id: str
purpose: str
page_id: str
current_url: str
step: str
challenge_detected: bool
challenge_attempts: int
challenge_status: Literal[
"not-needed", "pending", "resolved", "review", "denied"
]
error_code: str | None
final_assertion_passed: bool
Do not add the CapSolver credential, solution token, cookies, or raw page content. Store browser objects in an application-owned registry keyed by page_id; graph checkpoints should contain only the opaque identifier.
Build a deterministic authorization node
Authorization should run before any challenge tool:
python
from urllib.parse import urlparse
ALLOWED_HOSTS = {"staging.example.com", "research.example.com"}
ALLOWED_PURPOSES = {"qa-validation", "public-data-research"}
def authorize_challenge(state: AgentState) -> AgentState:
host = urlparse(state["current_url"]).hostname
attempts = state.get("challenge_attempts", 0)
if host not in ALLOWED_HOSTS:
return {**state, "challenge_status": "denied", "error_code": "domain"}
if state.get("purpose") not in ALLOWED_PURPOSES:
return {**state, "challenge_status": "denied", "error_code": "purpose"}
if attempts >= 2:
return {**state, "challenge_status": "review", "error_code": "retry-limit"}
return {**state, "challenge_status": "pending"}
This node is syntax-validated and independent of the model. In production, load policy from versioned configuration and reject unknown fields.
Register a browser session outside graph state
python
class BrowserRegistry:
def __init__(self):
self._pages = {}
def register(self, page_id: str, page) -> None:
self._pages[page_id] = page
def get(self, page_id: str):
if page_id not in self._pages:
raise KeyError("browser page is not registered")
return self._pages[page_id]
async def remove(self, page_id: str) -> None:
page = self._pages.pop(page_id, None)
if page is not None:
await page.close()
The registry prevents serialization of a Playwright Page and gives the host one place to enforce cleanup.
Redeem Your CapSolver Bonus Code
Boost your automation budget instantly!
Use bonus code CAP26 when topping up your CapSolver account to get an extra 5% bonus on every recharge โ with no limits.
Redeem it now in your CapSolver Dashboard
Implement the CapSolver recovery node
CapSolver's Core SDK documents detect(page) and solve_on_page(page) for browser mode. The adapter below uses those methods and returns only a graph decision:
python
import os
from capsolver_core import create_capsolver
async def solve_turnstile_node(
state: AgentState,
registry: BrowserRegistry,
) -> AgentState:
page = registry.get(state["page_id"])
attempts = state.get("challenge_attempts", 0) + 1
async with create_capsolver(
api_key=os.environ["CAPSOLVER_API_KEY"],
default_timeout=120,
polling_interval=5,
) as cap:
detected = await cap.detect(page)
if not detected:
return {
**state,
"challenge_attempts": attempts,
"challenge_status": "not-needed",
"error_code": None,
}
results = await cap.solve_on_page(page)
failures = [item for item in results if item.error or not item.filled]
if failures:
return {
**state,
"challenge_attempts": attempts,
"challenge_status": "review" if attempts >= 2 else "pending",
"error_code": "fill-back-failed",
}
return {
**state,
"challenge_attempts": attempts,
"challenge_status": "resolved",
"error_code": None,
}
The code has been syntax-checked but not credential-run. A live test requires an approved page and secret. The graph never receives solution.token.
Verify the page outcome
A filled token is an intermediate result. Verify the application's expected state:
python
async def verify_page_node(
state: AgentState,
registry: BrowserRegistry,
) -> AgentState:
page = registry.get(state["page_id"])
try:
await page.get_by_test_id("authorized-content").wait_for(timeout=15_000)
return {
**state,
"final_assertion_passed": True,
"step": "continue",
"error_code": None,
}
except Exception:
return {
**state,
"final_assertion_passed": False,
"challenge_status": "review",
"error_code": "page-assertion-failed",
}
Use an assertion owned by your application. Avoid selectors that expose personal or sensitive page content in logs.
Assemble the LangGraph workflow
python
from langgraph.graph import END, StateGraph
def route_after_authorization(state: AgentState) -> str:
if state["challenge_status"] == "pending":
return "solve"
if state["challenge_status"] in {"denied", "review"}:
return "human_review"
return "verify"
def route_after_solve(state: AgentState) -> str:
if state["challenge_status"] == "resolved":
return "verify"
if state["challenge_status"] == "pending":
return "authorize"
return "human_review"
def build_graph(authorize, solve, verify, human_review):
graph = StateGraph(AgentState)
graph.add_node("authorize", authorize)
graph.add_node("solve", solve)
graph.add_node("verify", verify)
graph.add_node("human_review", human_review)
graph.set_entry_point("authorize")
graph.add_conditional_edges(
"authorize",
route_after_authorization,
{"solve": "solve", "verify": "verify", "human_review": "human_review"},
)
graph.add_conditional_edges(
"solve",
route_after_solve,
{"authorize": "authorize", "verify": "verify", "human_review": "human_review"},
)
graph.add_edge("verify", END)
graph.add_edge("human_review", END)
return graph.compile()
The injected functions can close over the browser registry. Dependency injection makes policy and failure routes testable without a live service.
Add a human-review node
The reviewer should receive:
- request ID;
- approved purpose;
- hostname;
- attempted action;
- retry count;
- redacted error category;
- safe screenshot reference, if permitted;
- proposed next step.
The reviewer should not receive the CapSolver credential or solution token. A state-changing action such as submission, purchase, account change, or message send should require its own authorization even after verification succeeds.
Test the graph without calling external services
Unit tests can replace the solve node with deterministic stubs:
python
async def solved_stub(state: AgentState) -> AgentState:
return {
**state,
"challenge_attempts": state.get("challenge_attempts", 0) + 1,
"challenge_status": "resolved",
"error_code": None,
}
async def failed_stub(state: AgentState) -> AgentState:
return {
**state,
"challenge_attempts": state.get("challenge_attempts", 0) + 1,
"challenge_status": "review",
"error_code": "fixture-failure",
}
Test approved and denied domains, unsupported purposes, retry exhaustion, missing browser pages, a resolved challenge with a failed page assertion, and cleanup after terminal states.
Use token mode when page parameters are known
Browser mode is appropriate when the agent already controls a Playwright page. Token mode can be simpler when your application knows the Turnstile page URL and public site key. CapSolver documents the AntiTurnstileTaskProxyLess task with required websiteURL and websiteKey, plus optional metadata.action and metadata.cdata.
Do not let the model invent these fields. Extract them deterministically from the approved page or application configuration.
Observability without secret leakage
Trace:
- graph node;
- request ID;
- policy decision;
- hostname;
- challenge type;
- attempt number;
- elapsed time;
- error category;
- fill-back boolean;
- final assertion boolean.
Do not trace prompts containing credentials, browser cookies, raw tokens, or unredacted form data. Define retention and access rules for screenshots and DOM evidence.
Failure modes and fixes
No challenge detected
Confirm that the page finished loading, the browser uses the intended session, and the SDK version supports the challenge type. Treat an empty detection result as not-needed only when the page assertion can still pass.
Task fails before ready
Record the error category, compare parameters with current CapSolver documentation, and stop after the retry budget. Do not increase retries automatically.
Token fill-back fails
Keep the same browser page, review callback or widget behavior, and verify that page navigation did not replace the context.
Graph loops indefinitely
Store and enforce challenge_attempts. Route to human review after the configured limit.
Verification succeeds but business action fails
Keep CAPTCHA recovery separate from the downstream action. The graph should surface the action's own error instead of re-solving the challenge.
Production checklist
- Pin dependency versions or commits.
- Use separate staging and production policies.
- Store secrets outside graph state.
- Restrict domains and purposes.
- Set per-call and total deadlines.
- Preserve one browser session.
- Redact tokens and cookies.
- Verify the page after fill-back.
- Add human review for consequential actions.
- Test cleanup and cancellation.
- Monitor denial, error, recovery, and assertion rates.
- Review policy changes like code.
Conclusion: make challenge handling a graph recovery state
A LangGraph Cloudflare Turnstile integration is most reliable when it behaves like a finite recovery workflow: detect, authorize, solve, verify, continue, or stop. The graph provides routing and observability; deterministic code provides policy; CapSolver provides the documented recognition layer.
Use CapSolver only for lawful, authorized automation. Review the current agent tools documentation, Core SDK guide, and related CapSolver blog tutorials before pinning an implementation.
FAQ
Q: Does LangGraph solve Cloudflare Turnstile by itself?
No. LangGraph controls workflow state and routing; the CapSolver adapter calls the recognition service and browser methods.
Q: Should the solution token be returned to the model?
No. Apply it inside the controlled browser adapter and return only status, error category, and verification outcome.
Q: Which CapSolver method is used with Playwright?
The current Core SDK documents detect(page), get_captcha_info(page), and solve_on_page(page) for browser mode.
Q: How many retries should the graph allow?
Use a small explicit budget based on your workflow and route to review rather than allowing an unbounded loop.
Q: Can the agent call the recovery node for any URL?
No. Enforce a deterministic domain and purpose allowlist before the node can invoke CapSolver.
Q: What proves the challenge was handled successfully?
A page-level application assertion proves workflow recovery; a provider status or filled token alone is not sufficient.
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 CAPTCHA with TinyFish AgentQL โ Step-by-Step Guide Using CapSolver
Learn how to integrate CapSolver with TinyFish AgentQL to automatically solve CAPTCHAs like reCAPTCHA and Cloudflare Turnstile. Step-by-step tutorial with Python and JavaScript SDK examples for seamless AI-powered web automation.

Ethan Collins
05-Aug-2026

How to Solve CAPTCHA in LlamaIndex Agents
Integrate CAPTCHA solving into LlamaIndex agents using FunctionTool and CapSolver for web data ingestion pipelines.

Ethan Collins
31-Jul-2026

How to Solve CAPTCHA with MCP: CapSolver Model Context Protocol Service
Set up CapSolver MCP service for zero-code CAPTCHA solving in Claude Desktop, Cursor, and any MCP client.

Ethan Collins
31-Jul-2026

How to Solve reCAPTCHA v3 in OpenAI Agents SDK
Generate high-score reCAPTCHA v3 tokens in OpenAI Agents SDK using CapSolver function_tool.

Ethan Collins
30-Jul-2026

How to Solve Cloudflare Turnstile in CrewAI Agents
Integrate Cloudflare Turnstile solving into CrewAI multi-agent workflows using CapSolver.

Ethan Collins
30-Jul-2026

How to Solve CAPTCHA in AutoGen Agents
Complete guide to integrating CAPTCHA solving into Microsoft AutoGen multi-agent conversations using CapSolver with register_function and group chat patterns.

Ethan Collins
29-Jul-2026


