AWS WAF CAPTCHA is Amazon's bot protection layer, deployed in front of APIs, web apps, and ALBs running on AWS infrastructure. It uses an audio/visual CAPTCHA challenge with token-based verification. If you're building automation against AWS-hosted services, this guide shows you how to handle it.
How AWS WAF CAPTCHA works
AWS WAF CAPTCHA operates differently from reCAPTCHA or hCaptcha:
- The WAF intercepts requests and redirects to a challenge page when bot signals are detected
- On solving, AWS sets a aws-waf-token cookie and a x-aws-waf-token header
- Subsequent requests within the token TTL pass through without re-challenge
- Token TTL is configured per WAF rule (typically 300–3600 seconds)
The challenge itself is a puzzle or image CAPTCHA served by captcha.amazonaws.com.
Solving AWS WAF CAPTCHA with Ocilar
cURL
curl -X POST https://api.ocilar.com/api/v1/solve/aws-waf \
-H "X-API-Key: sk-your_key" \
-H "Content-Type: application/json" \
-d '{
"page_url": "https://target-site.com/protected-page",
"context": "aws-waf-captcha"
}'
# Response
{
"token": "KQoAns...base64...",
"cookie": "aws-waf-token=KQoAns...",
"latency_ms": 4200,
"credits_used": 1
} Python — automated request flow
import requests
from ocilar import OcilarClient
client = OcilarClient(api_key="sk-your_key")
def fetch_with_waf_bypass(url: str) -> requests.Response:
"""
Fetch a URL protected by AWS WAF CAPTCHA.
Automatically solves the CAPTCHA and retries with the token.
"""
session = requests.Session()
# First attempt — may be blocked by WAF
resp = session.get(url)
# Check if WAF redirected us to a CAPTCHA challenge
if "captcha.amazonaws.com" in resp.url or resp.status_code == 405:
print("AWS WAF CAPTCHA detected, solving...")
result = client.solve_aws_waf(page_url=url)
# Set the WAF token cookie
session.cookies.set(
"aws-waf-token",
result.token,
domain=url.split("/")[2]
)
# Retry the original request
resp = session.get(url)
print(f"Status after solve: {resp.status_code}")
return resp
response = fetch_with_waf_bypass("https://target-site.com/api/data")
print(response.json()) Python — Playwright with AWS WAF
import asyncio
from playwright.async_api import async_playwright
from ocilar import OcilarClient
client = OcilarClient(api_key="sk-your_key")
async def browse_waf_protected():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context()
page = await context.new_page()
# Attempt to navigate
response = await page.goto("https://target-site.com/protected")
# Detect WAF challenge
current_url = page.url
if "captcha.amazonaws.com" in current_url or "waf-challenge" in current_url:
print("WAF challenge detected, solving...")
result = client.solve_aws_waf(page_url="https://target-site.com/protected")
# Inject the token as a cookie
await context.add_cookies([{
"name": "aws-waf-token",
"value": result.token,
"domain": "target-site.com",
"path": "/"
}])
# Reload the original page
await page.goto("https://target-site.com/protected")
content = await page.content()
print("Page loaded, length:", len(content))
await browser.close()
asyncio.run(browse_waf_protected()) Node.js
import { OcilarClient } from '@ocilar/sdk'
import axios from 'axios'
const client = new OcilarClient({ apiKey: 'sk-your_key' })
async function fetchWithWafBypass(url) {
const instance = axios.create()
try {
return await instance.get(url)
} catch (err) {
if (err.response?.status === 405 || err.message.includes('captcha')) {
console.log('WAF detected, solving...')
const { token } = await client.solveAwsWaf({ pageUrl: url })
// Retry with token header
return instance.get(url, {
headers: { 'x-aws-waf-token': token },
withCredentials: true
})
}
throw err
}
}
const response = await fetchWithWafBypass('https://target-site.com/api/data')
console.log(response.data) Token caching — avoid solving on every request
AWS WAF tokens are valid for the configured TTL (often 5–60 minutes). Cache and reuse them:
import time
from ocilar import OcilarClient
client = OcilarClient(api_key="sk-your_key")
_token_cache = {} # domain -> (token, expires_at)
def get_waf_token(page_url: str, ttl_seconds: int = 300) -> str:
domain = page_url.split("/")[2]
now = time.time()
if domain in _token_cache:
token, expires_at = _token_cache[domain]
if now < expires_at - 30: # 30s buffer
return token
result = client.solve_aws_waf(page_url=page_url)
_token_cache[domain] = (result.token, now + ttl_seconds)
return result.token Common use cases
Price monitoring & e-commerce data
Many retail and marketplace sites use AWS WAF to block scrapers. Token caching means you solve once and make hundreds of requests before needing to solve again.
API testing against WAF-protected staging environments
Internal tools and CI pipelines testing against AWS-hosted staging environments can hit WAF challenges. Automate the solve step so tests don't block on manual CAPTCHA.
Compliance and audit crawlers
Security scanners and compliance tools (accessibility audits, broken link checkers) that crawl AWS-hosted sites need to handle WAF challenges transparently.
Pricing
AWS WAF CAPTCHA solving costs $2.00 per 1,000 solves. Volume discounts available.
| Service | AWS WAF support | Price / 1K |
|---|---|---|
| 2Captcha | Limited | $2.99 |
| CapSolver | Yes | $2.80 |
| Anti-Captcha | No | — |
| Ocilar | Yes | $2.00 |
FAQ
How is AWS WAF different from Cloudflare Turnstile?
Both protect web apps but work differently at the infrastructure level. AWS WAF is deployed on AWS ALB/CloudFront; Cloudflare Turnstile runs on Cloudflare's network. Each requires a different solver. Ocilar supports both — see the Turnstile guide.
What if the WAF uses a JS challenge instead of a visual CAPTCHA?
AWS WAF also issues silent JS challenges (no visual CAPTCHA). These are handled differently — the browser must execute challenge JS and return a token. Use the Playwright approach above which runs real browser JS.
Can I solve AWS WAF in headless mode?
Yes. The Ocilar solver handles the challenge server-side — your automation code just injects the resulting token. No headful browser required for the solve step itself.
What's the token TTL?
Token TTL is set by the site's WAF configuration. Common values: 300s (5 min), 900s (15 min), 3600s (1 hour). Check response headers for x-amzn-waf-action to detect WAF behavior.