AWS WAF CAPTCHA task at a glance
AwsCaptchaCookie solves the "select all the …" image grid that AWS WAF shows when a web ACL rule uses the CAPTCHA action. It returns the aws-waf-token cookie value that proves the grid was completed.
| Field | Required | Notes |
|---|---|---|
type |
yes | AwsCaptchaCookie |
websiteURL |
yes | The protected URL that answers with the CAPTCHA page |
userAgent |
yes | Your browser's User-Agent; use it again when you replay the cookie |
proxy |
yes | Your sticky proxy, as a proxy string or split proxy fields |
cookies |
no | Existing session cookies as a Cookie header value or NDJSON (one JSON object per line) |
There is no site key, IV, context or script URL to collect. The solver loads the protected URL through your proxy and reads the CAPTCHA configuration from the page itself. The AWS CAPTCHA API reference has the complete request and result examples.
Image grids it answers
The puzzle is a 3×3 grid of nine pictures with one target category, for example "select all the clocks". The solver picks the tiles that show the target and confirms the selection, then repeats for a replacement grid if AWS issues one.

Other layouts and multi-target puzzles are outside the current integration. Those tasks fail instead of returning a guessed answer, and a failed task returns its original charge once.
Use the aws-waf-token result
When ready, set solution["aws-waf-token"] as the aws-waf-token cookie on the protected site. Merge the name-to-value entries in solution.cookies when present, then repeat the request through the same proxy with the same userAgent. No token alias or new user agent is returned.
A valid WAF cookie does not bypass a login or any other application rule. Keep the application's own session cookies when your flow needs them.
Pick the right AWS task
| The protected URL answers with | Use |
|---|---|
| A visual CAPTCHA page | AwsCaptchaCookie (this page) |
| The silent JavaScript challenge | AwsChallengeCookie |
If the URL serves only the silent challenge, AwsCaptchaCookie fails instead of reporting a solved CAPTCHA. Execution can take up to the 120-second CAPTCHA budget, so keep polling getTaskResult rather than submitting the same task again.
For the response headers that tell the two actions apart, how token lifetime works, and a failure checklist, read the AWS WAF CAPTCHA guide.
Request and ready result
Send the actual CAPTCHA-protected URL to POST /createTask. The values below are placeholders; cookies is optional and carries the existing blocked session:
{
"clientKey": "sur_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"task": {
"type": "AwsCaptchaCookie",
"websiteURL": "https://example.com/protected",
"userAgent": "YOUR_SUPPORTED_USER_AGENT",
"proxy": "http://user:password@proxy.example.com:8080",
"cookies": "session=YOUR_SESSION"
}
}
Poll POST /getTaskResult with clientKey and the returned taskId until it is ready or fails:
{
"errorId": 0,
"status": "ready",
"solution": {
"aws-waf-token": "<aws-waf-token-value>"
}
}
Keep the value unchanged and use it under the cookie name aws-waf-token. Do not send it as an image answer, copy another task's cookie, or substitute the silent Challenge task for the visual CAPTCHA.
Complete solve and replay example
The script sends the current session to the solve, polls the result, replaces aws-waf-token while preserving the other cookies, and retries the page through the same proxy and User-Agent. Set SUCCESS_TEXT to text that appears only in an accepted application response.
# aws-captcha-python.py - solve AWS WAF CAPTCHA and request the protected page with the
# aws-waf-token cookie.
# Install: pip install requests
# Run: python aws-captcha-python.py
import time
import requests
# Your SurSolver API key.
CLIENT_KEY = "sur_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# The protected page that answers with the challenge.
TARGET_URL = "https://example.com/page"
# The proxy for the solve and the replay: http://user:pass@host:port or socks5://host:port.
PROXY_URL = "http://username:password@proxy.example.com:8080"
# Existing session cookies as a Cookie header value (name=value; name2=value2).
SESSION_COOKIES = None
# The User-Agent the solve runs with; send the result with this same user agent.
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/155.0.0.0 Safari/537.36"
# Text that appears only in a response the site accepted.
SUCCESS_TEXT = "PASTE_ACCEPTED_MARKER"
API = "https://api.sursolver.com"
POLL_ATTEMPTS = 60
def call(path, body):
# API errors are HTTP 200 with errorId 1; anything else is a transport failure.
response = requests.post(f"{API}{path}", json=body, timeout=30)
response.raise_for_status()
data = response.json()
if data["errorId"] != 0:
raise RuntimeError(f'{data["errorCode"]}: {data["errorDescription"]}')
return data
def solve(task):
created = call("/createTask", {"clientKey": CLIENT_KEY, "task": task})
for _ in range(POLL_ATTEMPTS):
result = call("/getTaskResult", {"clientKey": CLIENT_KEY, "taskId": created["taskId"]})
if result["status"] == "ready":
return result["solution"]
time.sleep(3)
raise TimeoutError(f'task {created["taskId"]} was not ready in {POLL_ATTEMPTS} polls')
def without_empty(fields):
# Optional task fields are sent only when they have a value.
return {name: value for name, value in fields.items() if value not in (None, "")}
PROXIES = {"http": PROXY_URL, "https": PROXY_URL}
def main():
solution = solve({
"type": "AwsCaptchaCookie",
"websiteURL": TARGET_URL,
"userAgent": USER_AGENT,
**without_empty({
"cookies": SESSION_COOKIES,
}),
"proxy": PROXY_URL,
})
# Replay with the user agent the solve ran with, through the same proxy.
headers = {"User-Agent": USER_AGENT}
cookies = dict(part.strip().split("=", 1) for part in (SESSION_COOKIES or "").split(";")
if "=" in part)
cookies["aws-waf-token"] = solution["aws-waf-token"]
cookies = {
**cookies,
**solution.get("cookies", {}),
"aws-waf-token": solution["aws-waf-token"],
}
response = requests.get(
TARGET_URL,
headers=headers,
cookies=cookies,
proxies=PROXIES,
timeout=30,
)
if not response.ok or SUCCESS_TEXT not in response.text:
raise SystemExit(f"The site did not accept the result (HTTP {response.status_code}).")
print("The site accepted the result.")
if __name__ == "__main__":
main()
SESSION_COOKIES in this script is a Cookie header string. For browser automation, install the solved cookie on the protected origin in the same context, then repeat the protected action.
Troubleshooting
| Symptom | Check | Correction |
|---|---|---|
| No image puzzle is reached | The response is the silent challenge or an ordinary page | Choose the task matching the current WAF action |
| Unsupported puzzle failure | Layout is not a supported single-target 3×3 grid | Do not treat it as a solved CAPTCHA |
| Cookie is generated but the page still blocks | Exit IP, User-Agent or cookie domain changed | Keep the original proxy session and replay identity |
| HTTP 200 still contains a challenge | Status alone was treated as acceptance | Check an application-specific response marker |
| A task is still processing | The visual solve has not reached its terminal state | Keep polling the same task within its budget; do not create duplicates |