> For the complete documentation index, see [llms.txt](https://docs.hypersolutions.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hypersolutions.co/start-here/script-hash.md).

# Script Hash

Send the sha256 of a challenge script as scriptHash instead of the full script, and retry with the script when the API answers 422 script\_required.

Endpoints that take a challenge script also accept **`scriptHash`**: the lowercase hex sha256 of the script exactly as the site served it. Challenge scripts are about 600 to 800 KB; the hash is 64 bytes. Once any client has sent a script, every client can send its hash instead.

{% hint style="success" %}
The SDKs do this for you from Go `v4.1.0`, Python `hyper-sdk` `4.1.0` and JS / TS `hyper-sdk-js` `5.1.0`. Keep passing `script` as today: the SDK sends the hash first and the script only when the API asks for it. No code changes are needed.
{% endhint %}

### Which endpoints accept it

| Product   | Endpoints                                                      | Rule                                                                                                                                                 |
| --------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Akamai    | `/v2/sensor`, `/sbsd`, `/v1/telemetry`, `/v1/telemetry/params` | `script`, `scriptHash` and `context` are mutually exclusive. The first request of a chain sends `script` or `scriptHash`, later ones send `context`. |
| DataDome  | `/interstitial`, `/slider`                                     | Never both. Without either, a fixed bundle version is used; always send one.                                                                         |
| Incapsula | `/reese84`, `/reese84safari`                                   | Exactly one of `script` and `scriptHash` is required.                                                                                                |

Akamai v1.75 sensors take no script. `/utmvc`, `/tags` and the Kasada endpoints are unchanged.

### How it works

1. Compute the sha256 of the script: the UTF-8 bytes of the exact string you would otherwise send as `script`, encoded as 64 lowercase hex characters. Do not trim, reformat or re-encode the script first.
2. Send the request with `scriptHash` in place of `script`.
3. If the API answers **HTTP 422** with `{"error": "script_required"}`, repeat the same request once with `script` instead of `scriptHash`. The 422 is not billed.

After that retry, the API has the script and the hash works for every client. A script is kept for at least 7 days. If it has expired, the API answers `script_required` again and the same retry stores it once more.

Some requests are solved in a real browser on our side. Those need the script itself and also answer `script_required` to a hash-only request; the same retry handles them, so treat every `script_required` the same way.

### Errors

| Status | Body                                        | Cause                                                                              |
| ------ | ------------------------------------------- | ---------------------------------------------------------------------------------- |
| `422`  | `{"error": "script_required"}`              | The API needs the script for this request. Retry once with `script`.               |
| `400`  | `{"error": "..."}`                          | `script` and `scriptHash` sent together, or on Akamai `scriptHash` with `context`. |
| `400`  | `{"error": "..."}`                          | `scriptHash` is not 64 lowercase hex characters.                                   |
| `400`  | `{"error": "missing script or scriptHash"}` | Incapsula: neither was sent.                                                       |

### Example

The examples call Incapsula's `/reese84`; the same pattern works for every endpoint above. `body` holds the rest of the request fields.

{% tabs %}
{% tab title="Golang" %}
{% code overflow="wrap" %}

```go
import (
    "bytes"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "net/http"
)

func scriptHash(script string) string {
    sum := sha256.Sum256([]byte(script))
    return hex.EncodeToString(sum[:])
}

func post(url, apiKey string, body map[string]any) (*http.Response, error) {
    b, err := json.Marshal(body)
    if err != nil {
        return nil, err
    }
    req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(b))
    if err != nil {
        return nil, err
    }
    req.Header.Set("content-type", "application/json")
    req.Header.Set("x-api-key", apiKey)
    return http.DefaultClient.Do(req)
}

func generate(apiKey, script string, body map[string]any) (*http.Response, error) {
    const url = "https://incapsula.hypersolutions.co/reese84"

    body["scriptHash"] = scriptHash(script)
    res, err := post(url, apiKey, body)
    if err != nil || res.StatusCode != http.StatusUnprocessableEntity {
        return res, err
    }

    var apiErr struct{ Error string `json:"error"` }
    json.NewDecoder(res.Body).Decode(&apiErr)
    res.Body.Close()
    if apiErr.Error != "script_required" {
        return res, nil
    }

    // The API has not seen this script yet: send it once.
    delete(body, "scriptHash")
    body["script"] = script
    return post(url, apiKey, body)
}
```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code overflow="wrap" %}

```python
import hashlib
import requests

URL = "https://incapsula.hypersolutions.co/reese84"

def script_hash(script: str) -> str:
    return hashlib.sha256(script.encode("utf-8")).hexdigest()

def generate(api_key: str, script: str, body: dict) -> requests.Response:
    headers = {"content-type": "application/json", "x-api-key": api_key}

    res = requests.post(URL, json={**body, "scriptHash": script_hash(script)}, headers=headers)
    if res.status_code == 422 and res.json().get("error") == "script_required":
        # The API has not seen this script yet: send it once.
        res = requests.post(URL, json={**body, "script": script}, headers=headers)
    return res
```

{% endcode %}
{% endtab %}

{% tab title="JS / TS" %}
{% code overflow="wrap" %}

```javascript
import { createHash } from "node:crypto";

const URL = "https://incapsula.hypersolutions.co/reese84";

function scriptHash(script) {
    return createHash("sha256").update(script, "utf8").digest("hex");
}

async function generate(apiKey, script, body) {
    const post = (payload) => fetch(URL, {
        method: "POST",
        headers: { "content-type": "application/json", "x-api-key": apiKey },
        body: JSON.stringify(payload),
    });

    let res = await post({ ...body, scriptHash: scriptHash(script) });
    if (res.status === 422) {
        const { error } = await res.clone().json();
        if (error === "script_required") {
            // The API has not seen this script yet: send it once.
            res = await post({ ...body, script });
        }
    }
    return res;
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

On Akamai, only the first request of a chain carries `script` or `scriptHash`; keep sending `context` on the requests after it as before.

{% hint style="info" %}
Hash the script as you received it from the site. If your HTTP client decodes the body into a string, hash that string's UTF-8 bytes, which is what the API hashes when you send the same string as `script`.
{% endhint %}
