> Part of the walkerOS documentation. Project overview and full index: <https://www.walkeros.io/llms.txt>

# Fingerprint

<!-- -->

[Server](#)[ ](https://github.com/elbwalker/walkerOS/tree/main/packages/server/transformers/fingerprint)

<!-- -->

[Source code](https://github.com/elbwalker/walkerOS/tree/main/packages/server/transformers/fingerprint)[ ](https://www.npmjs.com/package/@walkeros/server-transformer-fingerprint)

<!-- -->

[Package](https://www.npmjs.com/package/@walkeros/server-transformer-fingerprint)

Hashes configurable request fields (IP address, user agent, date, etc.) into a deterministic identifier and stores it on the event. No cookies, no PII stored. The same combination of inputs always produces the same hash, enabling server-side session continuity, cookie-free analytics, and cross-domain stitching without client-side IDs.

## Installation[​](#installation "Direct link to Installation")

```
npm install @walkeros/server-transformer-fingerprint
```

* Integrated
* Bundled

```
import { startFlow } from '@walkeros/collector';
import { transformerFingerprint } from '@walkeros/server-transformer-fingerprint';

await startFlow({
  transformers: {
    fingerprint: {
      code: transformerFingerprint,
      config: {
        settings: {
          fields: ['ingest.ip', 'ingest.userAgent'],
          output: 'user.hash',
          length: 16,
        },
      },
    },
  },
});
```

Add to your `flow.json`:

```
"transformers": {
  "fingerprint": {
    "package": "@walkeros/server-transformer-fingerprint",
    "config": {
      "settings": {
        "fields": ["ingest.ip", "ingest.userAgent"],
        "output": "user.hash",
        "length": 16
      }
    }
  }
}
```

[See bundled mode setup](https://www.walkeros.io/docs/getting-started/modes/bundled.md) | [CLI reference](https://www.walkeros.io/docs/apps/cli.md)

## Configuration[​](#configuration "Direct link to Configuration")

This <!-- -->transformer<!-- --> uses the standard <!-- -->transformer<!-- --> config wrapper (consent, data, env, id, ...). For the shared fields see [transformer<!-- --> configuration](https://www.walkeros.io/docs/transformers.md#configuration). Package-specific fields live under `config.settings` and are listed below.

## Settings[​](#settings "Direct link to Settings")

| Property  | Type         | Description                                                                                                 | More |
| --------- | ------------ | ----------------------------------------------------------------------------------------------------------- | ---- |
| `fields*` | `Array<any>` | Fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }. |      |
| `output`  | `string`     | Dot-notation path where hash is stored on the event. Default: "user.hash"                                   |      |
| `length`  | `integer`    | Truncate hash to this length. Default: full 64-char SHA-256 hash                                            |      |

\* Required fields

## Mapping[​](#mapping "Direct link to Mapping")

This package does not define custom rule-level settings. For the standard rule fields (consent, condition, data, batch, name, policy) see [mapping](https://www.walkeros.io/docs/mapping.md).

## Examples

### IP anonymization

Privacy-preserving fingerprint using key+fn pattern: fn truncates IP to /24 subnet before hashing, so 10.0.42.\* users share a hash. Config: fields: \[{ key: "ingest.ip", fn: ip => ip.replace(/\\.\d+$/, ".0") }, "ingest.userAgent"]

Event

```
{
  "name": "page view",
  "data": {
    "domain": "www.example.com",
    "title": "Privacy Policy",
    "id": "/privacy"
  },
  "id": "ev-1700000602",
  "trigger": "load",
  "entity": "page",
  "action": "view",
  "timestamp": 1700000602,
  "source": {
    "type": "express",
    "platform": "server"
  }
}
```

Out

```
return {
  "event": {
    "name": "page view",
    "data": {
      "domain": "www.example.com",
      "title": "Privacy Policy",
      "id": "/privacy"
    },
    "user": {
      "hash": "44d9154b9a9b3792"
    },
    "id": "ev-1700000602",
    "trigger": "load",
    "entity": "page",
    "action": "view",
    "timestamp": 1700000602,
    "source": {
      "type": "express",
      "platform": "server"
    }
  }
}
```

### Server fingerprint

Standard server fingerprint using ingest.ip and ingest.userAgent. Requires source config.ingest.

Event

```
{
  "name": "page view",
  "data": {
    "domain": "www.example.com",
    "title": "Getting Started",
    "id": "/docs/getting-started"
  },
  "id": "ev-1700000600",
  "trigger": "load",
  "entity": "page",
  "action": "view",
  "timestamp": 1700000600,
  "source": {
    "type": "express",
    "platform": "server"
  }
}
```

Out

```
return {
  "event": {
    "name": "page view",
    "data": {
      "domain": "www.example.com",
      "title": "Getting Started",
      "id": "/docs/getting-started"
    },
    "user": {
      "hash": "158f99cc06e33fd6"
    },
    "id": "ev-1700000600",
    "trigger": "load",
    "entity": "page",
    "action": "view",
    "timestamp": 1700000600,
    "source": {
      "type": "express",
      "platform": "server"
    }
  }
}
```

Fields are resolved from `{ event, ingest }` using walkerOS mapping. String values use dot notation (`"ingest.ip"`). Function values compute dynamically (`{ fn: () => new Date().getDate() }`).

## Daily rotation[​](#daily-rotation "Direct link to Daily rotation")

Without rotation, the same IP + user agent always produce the same hash, indefinitely. To limit persistence, add a daily rotation field:

```
settings: {
  fields: [
    'ingest.ip',
    'ingest.userAgent',
    { fn: () => new Date().toISOString().slice(0, 10) }, // "2024-01-15"
  ],
  output: 'user.hash',
  length: 16,
}
```

The hash resets each day, limiting cross-day tracking while maintaining session continuity within a day.

## IP anonymization[​](#ip-anonymization "Direct link to IP anonymization")

To avoid including the raw IP in the hash input, transform it before hashing:

```
settings: {
  fields: [
    { key: 'ingest.ip', fn: (ip) => ip?.split('.').slice(0, 3).join('.') }, // drops last octet
    'ingest.userAgent',
  ],
  output: 'user.hash',
}
```

## Result[​](#result "Direct link to Result")

The hash is stored at the configured `output` path on the event:

```
// Input event (before fingerprint transformer)
{ name: 'page view', entity: 'page', action: 'view', ... }

// Output event (after fingerprint transformer)
{ name: 'page view', entity: 'page', action: 'view', user: { hash: '158f99cc06e33fd6' }, ... }
```

If any field is missing (e.g., `ingest.ip` is undefined), it is treated as an empty string. The transformer never throws.
