> 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/challenge.md).

# Challenge

The end-to-end AWS WAF Challenge integration: detect it, extract the challenge\_url, solve it and replay the aws-waf-token as a cookie.

The step-by-step implementation of the AWS WAF **Challenge** (the silent JS proof-of-work check that issues the `aws-waf-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**.

> AWS WAF **CAPTCHA** (the image-recognition puzzle) is a **different mechanism** with its own endpoint and token — see the [Captcha](/aws-waf/captcha.md) guide.

## Step 1: Detect AWS WAF in your traffic

AWS WAF is served as an HTTP **405** (or 202) response with the header:

```
x-amzn-waf-action: challenge
```

The response body is an interstitial HTML that references the AWS WAF SDK:

```html
<script src="https://{id}.{region}.token.awswaf.com/{id}/{hashA}/{hashB}/challenge.js"></script>
```

**The `challenge_url` is NOT a fixed value** — the client must extract it from its own traffic, not hardcode it:

| Part                                         | Fixed?             | Detail                             |
| -------------------------------------------- | ------------------ | ---------------------------------- |
| `{id}` (1st host segment + 1st path segment) | **Fixed per site** | The AWS WAF deployment id          |
| `{region}` (2nd host segment)                | **Changes**        | AWS assigns the region dynamically |
| `{hashA}/{hashB}` (path)                     | **Changes**        | Per-session deployment hashes      |

## Step 2: Pick your user agent

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

```
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, or a version without a Chrome build) 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 `challenge_url` you extracted, the exact `page_url` the SDK runs on, 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{
			"challenge_url": "https://{id}.{region}.token.awswaf.com/{id}/{hashA}/{hashB}",
			"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",
		},
		"proxy": "http://user:pass@host:port",
	})

	req, _ := http.NewRequest("POST", "https://waf.apexsolutions.lol/payload", 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/payload",
    headers={"Authorization": "Bearer your-api-key"},
    json={
        "params": {
            "challenge_url": "https://{id}.{region}.token.awswaf.com/{id}/{hashA}/{hashB}",
            "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",
        },
        "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/payload", {
  method: "POST",
  headers: {
    "Authorization": "Bearer your-api-key",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    params: {
      challenge_url: "https://{id}.{region}.token.awswaf.com/{id}/{hashA}/{hashB}",
      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",
    },
    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-token`.

### Why `page_url` must be exact

The `page_url` lands in the encrypted fingerprint (`fp.location`) the WAF cross-checks. Sending a different URL than the page where the SDK actually runs **raises the proof-of-work difficulty** (e.g. `/` instead of `/sign_in` can go from `diff=1` to `diff=4`).

## Step 4: Replay the token as a cookie

Attach the token as the `aws-waf-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-token from the solve>"

resp = requests.get(
    "https://{target}/protected-page",
    headers={
        "Cookie": f"aws-waf-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. A token minted for a different IP or user agent is rejected by the WAF.

## Important Notes

{% hint style="info" %}
**The token is a cookie, not a backend value.** Replay it as the `aws-waf-token` cookie on the requests to the site — do not submit it to the site's API. Tokens are tied to the session: reuse the same user agent and proxy IP. If the site still serves the challenge, the token may have expired or the session drifted — solve again with a fresh token. Use a **sticky-session proxy** (same IP for the whole session): AWS WAF rate-limits per source IP.
{% endhint %}
