> 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/k4sada/mobile-user-agent.md).

# Mobile User-Agent

How the Kasada mobile SDK derives the user-agent and x-kpsdk-dv headers, and how to generate them with the Hyper Solutions SDKs.

On the native mobile app flow, the `user-agent` header Kasada sends to `/tl` and the protected endpoints is **not a readable UA string**. The Kasada 2.x mobile SDK replaces it with a 32-character hex hash derived from the device and app identity.

If you send a browser-style UA on a mobile Kasada flow, it will not match what the SDK produces, so generate the value instead of hardcoding one.

### How it is derived

The value is the lowercase hex **MD5** of the string:

```
<osVersion> / <bundleId> <appVersion>
```

Note the spaces: `/` between the OS version and the bundle id, and a single space before the app version.

| Component    | iOS                                           | Android                             |
| ------------ | --------------------------------------------- | ----------------------------------- |
| `osVersion`  | `UIDevice.systemVersion` (e.g. `18.0.1`)      | `Build.VERSION.SDK_INT` (e.g. `34`) |
| `bundleId`   | `CFBundleIdentifier` (e.g. `com.example.app`) | application package name            |
| `appVersion` | `CFBundleShortVersionString` (e.g. `9.15.0`)  | `versionName`                       |

{% hint style="info" %}
The hash is pure device and app identity, with no per-request nonce, so it is stable for a given `osVersion` + `bundleId` + `appVersion`. The dynamic challenge data lives in `x-kpsdk-cd`, `x-kpsdk-ct` and `x-kpsdk-im`, not in the user-agent.
{% endhint %}

For example, `18.0.1 / com.example.app 9.15.0` hashes to `7016adc4ba8d433533954ebad33be424`.

### Generating it with the SDKs

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

```go
import "github.com/Hyper-Solutions/hyper-sdk-go/v4/kasada"

userAgent := kasada.GenerateUserAgent("18.0.1", "com.example.app", "9.15.0")
// userAgent == "7016adc4ba8d433533954ebad33be424"
```

{% endcode %}
{% endtab %}

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

```python
from hyper_sdk import generate_kasada_user_agent

user_agent = generate_kasada_user_agent("18.0.1", "com.example.app", "9.15.0")
# user_agent == "7016adc4ba8d433533954ebad33be424"
```

{% endcode %}
{% endtab %}

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

```javascript
import { generateKasadaUserAgent } from 'hyper-sdk-js';

const userAgent = generateKasadaUserAgent({
    osVersion: '18.0.1',
    bundleId: 'com.example.app',
    appVersion: '9.15.0',
});
// userAgent === "7016adc4ba8d433533954ebad33be424"
```

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

Available in `hyper-sdk-go` v3.1.0, `hyper-sdk-js` 4.1.0, and `hyper_sdk` (Python) 3.1.0.

Send the returned value as the `user-agent` header on the mobile Kasada requests. When the payload was generated with a mobile UA, remember to send **only `st`** on `/cd` (see [KasadaPowInput](/k4sada/flow-2-fingerprint-endpoint.md)).

{% hint style="warning" %}
Use the real OS version, bundle id and app version of the device and build you are emulating. A single different byte in any field produces a completely different hash, and a value that does not match the rest of your mobile fingerprint can get the request flagged.
{% endhint %}

## The x-kpsdk-dv header

On iOS the Kasada SDK also sends an `x-kpsdk-dv` header, starting with the very first request to the protected site, before you call our API. It describes the device and the app: the iOS version, the model identifier, the bundle id and the app version.

### How it is derived

The value is the base64 of a small JSON object, XORed byte by byte with a key fixed in the Kasada SDK:

```json
{"osVersion":"18.0.1","hardwareId":"iPhone13,2","appId":"com.example.app","appVersion":"9.15.0"}
```

| Field        | Source                                                              | Example                  |
| ------------ | ------------------------------------------------------------------- | ------------------------ |
| `osVersion`  | `UIDevice.systemVersion`                                            | `18.0.1`                 |
| `hardwareId` | the model identifier from `utsname.machine`, not the marketing name | `iPhone13,2` (iPhone 12) |
| `appId`      | the app's bundle id, the same one the hashed user-agent uses        | `com.example.app`        |
| `appVersion` | the app's version string, the same one the hashed user-agent uses   | `9.15.0`                 |

`appId` and `appVersion` are part of the header from Kasada SDK `i-2.x` (the `x-kpsdk-v` value) onward, and a site on that SDK rejects a header without them. If you captured the header from the app you are emulating, send that exact value; otherwise generate one.

### Generating it with the SDKs

Build the user-agent and the header from the same values, so both describe the same app and device.

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

```go
import "github.com/Hyper-Solutions/hyper-sdk-go/v4/kasada"

userAgent := kasada.GenerateUserAgent("18.0.1", "com.example.app", "9.15.0")
// userAgent == "7016adc4ba8d433533954ebad33be424"

dv := kasada.GenerateDV("18.0.1", "iPhone13,2", "com.example.app", "9.15.0")
// dv == "QkZWEmcDRUBEDloaAg8FDhpTAwhEGxFFWRFcQAVCVHFXFAlBUDRRDl8DBgABUxYUGkxERn0HDwNEVFxAFgZAVglAXV0dV0MTG0gbAEEWYVZfEl1XVg8OFA1NHAxIBxFQ"
```

{% endcode %}
{% endtab %}

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

```python
from hyper_sdk import generate_kasada_dv, generate_kasada_user_agent

user_agent = generate_kasada_user_agent("18.0.1", "com.example.app", "9.15.0")
# user_agent == "7016adc4ba8d433533954ebad33be424"

dv = generate_kasada_dv("18.0.1", "iPhone13,2", "com.example.app", "9.15.0")
# dv == "QkZWEmcDRUBEDloaAg8FDhpTAwhEGxFFWRFcQAVCVHFXFAlBUDRRDl8DBgABUxYUGkxERn0HDwNEVFxAFgZAVglAXV0dV0MTG0gbAEEWYVZfEl1XVg8OFA1NHAxIBxFQ"
```

{% endcode %}
{% endtab %}

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

```javascript
import { generateKasadaDv, generateKasadaUserAgent } from 'hyper-sdk-js';

const device = {
    osVersion: '18.0.1',
    hardwareId: 'iPhone13,2',
    bundleId: 'com.example.app', // sent as appId
    appVersion: '9.15.0',
};

const userAgent = generateKasadaUserAgent(device);
// userAgent === "7016adc4ba8d433533954ebad33be424"

const dv = generateKasadaDv(device);
// dv === "QkZWEmcDRUBEDloaAg8FDhpTAwhEGxFFWRFcQAVCVHFXFAlBUDRRDl8DBgABUxYUGkxERn0HDwNEVFxAFgZAVglAXV0dV0MTG0gbAEEWYVZfEl1XVg8OFA1NHAxIBxFQ"
```

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

### Pass it to the payload request

Send the same value as `dv` when you generate the payload. The API then builds the payload on a real iPhone with the screen of the model you named, running the iOS version you named, so the payload and the header you already sent describe the same device.

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

```go
res, err := session.GenerateKasadaPayload(ctx, &hyper.KasadaPayloadInput{
    UserAgent:      userAgent, // the app's user-agent, see above
    IpsLink:        ipsLink,
    Script:         script,
    AcceptLanguage: "en-US,en;q=0.9",
    IP:             ip,
    Dv:             dv, // the x-kpsdk-dv value you send to the site
})
```

{% endcode %}
{% endtab %}

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

```python
from hyper_sdk import KasadaPayloadInput

result = session.generate_kasada_payload(KasadaPayloadInput(
    user_agent,  # the app's user-agent, see above
    ips_link,
    script,
    "en-US,en;q=0.9",
    ip,
    dv=dv,  # the x-kpsdk-dv value you send to the site
))
```

{% endcode %}
{% endtab %}

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

```javascript
import { KasadaPayloadInput, generateKasadaPayload } from 'hyper-sdk-js';

const result = await generateKasadaPayload(session, new KasadaPayloadInput(
    userAgent, // the app's user-agent, see above
    ipsLink,
    script,
    ip,
    'en-US,en;q=0.9',
    false, // strict
    dv,    // the x-kpsdk-dv value you send to the site
));
```

{% endcode %}
{% endtab %}

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

```json
{
  "userAgent": "7016adc4ba8d433533954ebad33be424",
  "ipsLink": "https://www.example.com/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/ips.js?...",
  "script": "...",
  "acceptLanguage": "en-US,en;q=0.9",
  "ip": "203.0.113.7",
  "dv": "QkZWEmcDRUBEDloaAg8FDhpTAwhEGxFFWRFcQAVCVHFXFAlBUDRRDl8DBgABUxYUGkxERn0HDwNEVFxAFgZAVglAXV0dV0MTG0gbAEEWYVZfEl1XVg8OFA1NHAxIBxFQ"
}
```

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

`dv` is optional and only read for a mobile app user-agent. Without it the API picks a device on the iOS version your user-agent names, or any recent iPhone when the user-agent names none (the hashed user-agent above names none), and that device may not be the model your header claims. A `dv` that does not decode is rejected with `invalid dv`.

{% hint style="warning" %}
Keep `x-kpsdk-dv` and your user-agent consistent. When the user-agent is the hashed form, the `osVersion`, bundle id and `appVersion` you hash it from are the `osVersion`, `appId` and `appVersion` that go in `x-kpsdk-dv`.
{% endhint %}
