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
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
}
}
}
}Configuration
This transformer uses the standard transformer config wrapper (consent, data, env, id, ...). For the shared fields see transformer configuration. Package-specific fields live under config.settings and are listed below.
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 |
Mapping
This package does not define custom rule-level settings. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.
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"]
{
"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"
}
}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.
{
"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"
}
}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
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
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
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.