> For the complete documentation index, see [llms.txt](https://docs.apexsolutions.lol/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apexsolutions.lol/aws-waf/captcha.md).

# Captcha

The end-to-end AWS WAF CAPTCHA integration: extract the interstitial params, solve the image puzzle and replay the token as a cookie.

The step-by-step implementation of the AWS WAF **CAPTCHA** (the interactive image puzzle: *"select all the tiles that contain X"*). The CAPTCHA is a different mechanism from the silent JS **Challenge** and yields an **`aws-waf-captcha-token`**. Each step describes what to do and why, with the actual requests against Apex. The full request and response reference lives in the **API reference**.

## Step 1: Detect the CAPTCHA in your traffic

The site serves the CAPTCHA as an HTTP **405** (or 202) response with the header:

```
x-amzn-waf-action: captcha
```

The response body is a **"Human Verification" interstitial** HTML that contains everything the solver needs:

```html
<script type="text/javascript">
    window.gokuProps = {
        "key":     "AQIDA...",
        "iv":      "NC4V...",
        "context": "OHD+..."
    };
</script>
<script src="https://{id}.{region}.token.awswaf.com/{id}/{hashA}/{hashB}/challenge.js"></script>
<script src="https://{id}.{region}.captcha.awswaf.com/{id}/{hashA}/{hashB}/captcha.js"></script>
```

The client extracts four values from this HTML:

| Param          | Source                                                          | Detail                                                               |
| -------------- | --------------------------------------------------------------- | -------------------------------------------------------------------- |
| `token_host`   | base of the `challenge.js` script URL (without `/challenge.js`) | The challenge/PoW host                                               |
| `captcha_host` | base of the `captcha.js` script URL (without `/captcha.js`)     | Same `{id}.{region}` prefix, `.captcha.awswaf.com`                   |
| `goku_props`   | the `window.gokuProps` object (`{key, iv, context}`)            | Sent in the verify step; **without it the server rejects the solve** |
| `page_url`     | the exact URL of the page/endpoint that returned the 405        | Sent as the `domain` of the `/problem`                               |

> These values are **per-session** (the region and path hashes rotate) — always parse them from a fresh 405 response, never hardcode them.

## Step 2: Pick your user agent

The solver builds the session from your user agent, so it must be **Google Chrome on Windows** and match the browser your client will actually use:

```
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36
```

Any other UA (Edge, macOS, Linux) is rejected with a clear error — the solve fails fast instead of returning a token that would not match your client.

## Step 3: Solve through Apex

Call Apex with the extracted params, your user agent, and your proxy:

{% tabs %}
{% tab title="Go" %}

```go
package main

import (
	"bytes"
	"encoding/json"
	"net/http"
)

func main() {
	body, _ := json.Marshal(map[string]any{
		"params": map[string]any{
			"token_host":    "https://{id}.{region}.token.awswaf.com/{id}/{hashA}/{hashB}",
			"captcha_host":  "https://{id}.{region}.captcha.awswaf.com/{id}/{hashA}/{hashB}",
			"goku_props": map[string]any{
				"key":     "AQIDA...",
				"iv":      "NC4V...",
				"context": "OHD+...",
			},
			"page_url":      "https://{target}/exact-page",
			"user_agent":    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
			"existing_token": "optional-aws-waf-token", // optional: if omitted the solver mints one first
		},
		"proxy": "http://user:pass@host:port",
	})

	req, _ := http.NewRequest("POST", "https://waf.apexsolutions.lol/captcha", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer your-api-key")
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
}
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

resp = requests.post(
    "https://waf.apexsolutions.lol/captcha",
    headers={"Authorization": "Bearer your-api-key"},
    json={
        "params": {
            "token_host": "https://{id}.{region}.token.awswaf.com/{id}/{hashA}/{hashB}",
            "captcha_host": "https://{id}.{region}.captcha.awswaf.com/{id}/{hashA}/{hashB}",
            "goku_props": {
                "key": "AQIDA...",
                "iv": "NC4V...",
                "context": "OHD+...",
            },
            "page_url": "https://{target}/exact-page",
            "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
            # optional: pass an existing aws-waf-token to skip the internal challenge
            # "existing_token": "optional-aws-waf-token",
        },
        "proxy": "http://user:pass@host:port",
    },
)

print(resp.status_code, resp.json())
```

{% endtab %}

{% tab title="JS/TS" %}

```jsts
const resp = await fetch("https://waf.apexsolutions.lol/captcha", {
  method: "POST",
  headers: {
    "Authorization": "Bearer your-api-key",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    params: {
      token_host: "https://{id}.{region}.token.awswaf.com/{id}/{hashA}/{hashB}",
      captcha_host: "https://{id}.{region}.captcha.awswaf.com/{id}/{hashA}/{hashB}",
      goku_props: {
        key: "AQIDA...",
        iv: "NC4V...",
        context: "OHD+...",
      },
      page_url: "https://{target}/exact-page",
      user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
      // optional: existing_token to skip the internal challenge
    },
    proxy: "http://user:pass@host:port",
  }),
});

const data = await resp.json();
console.log(data);
```

{% endtab %}
{% endtabs %}

The response gives you the `token` under `result` — the `aws-waf-captcha-token`.

## Step 4: Replay the token as a cookie

Attach the token as the `aws-waf-captcha-token` cookie on the requests your client sends to the protected page, using the same user agent and the same proxy IP:

```python
import requests

token = "<aws-waf-captcha-token from the solve>"

resp = requests.get(
    "https://{target}/protected-page",
    headers={
        "Cookie": f"aws-waf-captcha-token={token}",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
    },
)
print(resp.status_code, resp.text[:200])
```

Keep the session consistent: the same user agent, the same proxy IP, and the token cookie on every request.

## Important Notes

{% hint style="info" %}
**The CAPTCHA is an image-recognition puzzle**, not the silent JS Challenge — each has its own endpoint and token. The `existing_token` is **optional**: if you omit it, the solver mints the `aws-waf-token` (Challenge) internally first and then solves the CAPTCHA for you. The `user_agent` must be **Google Chrome on Windows**. The token is a cookie to replay on the protected site — keep the same user agent and sticky proxy IP for the whole session. There is **no `api_key` param**: the real browser flow does not send one to `/problem`.
{% endhint %}
