---
title: "Cloudflare WAF in Python"
description: "Solve a WAF challenge and apply the returned identity to a follow-up request."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.uncaptcha.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Cloudflare WAF in Python

This async example uses one sticky proxy and one browser identity to encounter
the challenge, solve it, and make the first request after the solve.

```bash
python -m pip install wreq
```

`asyncio` is included with Python 3.11 and later, so it does not need to be
installed separately.

```python title="cloudflare_waf.py"
import asyncio
import os
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit

from wreq import Client, Cookie, Emulation, Proxy

API_URL = "https://api.uncaptcha.io/v1/task/execute"
USER_AGENT = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/143.0.0.0 Safari/537.36"
)

def add_query_parameter(url: str, name: str, value: str) -> str:
parts = urlsplit(url)
query = dict(parse_qsl(parts.query, keep_blank_values=True))
query[name] = value
return urlunsplit(parts._replace(query=urlencode(query)))

async def solve_waf(
*,
url: str,
proxy: str,
challenge_html: str,
) -> dict:
async with Client(timeout=35) as api_client:
    response = await api_client.post(
        API_URL,
        headers={"X-Api-Key": os.environ["UNCAPTCHA_API_KEY"]},
        json={
            "task_type": "waf",
            "task_data": {
                "url": url,
                "proxy": proxy,
                "user_agent": USER_AGENT,
                "html": challenge_html,
            },
        },
    )
    payload = await response.json()

if not response.status.is_success() or not payload.get("success"):
    raise RuntimeError(payload.get("message", "unknown uncaptcha.io error"))
return payload["data"]["solution"]

async def main() -> None:
target = "https://example.com/protected"
proxy = os.environ["SOLVE_PROXY"]

async with Client(
    emulation=Emulation.Chrome143,
    user_agent=USER_AGENT,
    proxies=[Proxy.all(proxy)],
    cookie_store=True,
    timeout=30,
) as session:
    challenged = await session.get(target)
    if challenged.headers.get("Cf-Mitigated") != "challenge":
        raise RuntimeError("The response was not a Cloudflare challenge")

    solution = await solve_waf(
        url=target,
        proxy=proxy,
        challenge_html=await challenged.text(),
    )

    session.cookie_jar.add(
        Cookie("cf_clearance", solution["clearance"]),
        target,
    )
    if solution.get("cf_bm"):
        session.cookie_jar.add(
            Cookie("__cf_bm", solution["cf_bm"]),
            target,
        )

    referer = target
    if solution.get("cf_rt"):
        referer = add_query_parameter(
            target, "__cf_chl_tk", solution["cf_rt"]
        )

    response = await session.post(
        target,
        headers={**solution["headers"], "Referer": referer},
        form=solution.get("attributes", {}),
    )
    response.raise_for_status()
    print(response.status, response.url)

if __name__ == "__main__":
asyncio.run(main())
```

> **Keep the emulation aligned**
>
> `Emulation.Chrome143` gives wreq a Chrome 143 TLS and HTTP/2 fingerprint. Keep
> it aligned with `USER_AGENT` and the returned Client Hints, and do not change
> the proxy between the challenge and follow-up request.

If you do not need to control the follow-up request, use
[`wafauto`](/tasks/waf-auto) and decode `response.base64_body` instead.

Source: https://docs.uncaptcha.io/examples/python-cloudflare-waf/index.mdx
