Skip to main content

Fingerprint

Server Source code Package

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
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,
      },
    },
  },
},
});

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

PropertyTypeDescriptionMore
fields*Array<any>Fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }.
outputstringDot-notation path where hash is stored on the event. Default: "user.hash"
lengthintegerTruncate hash to this length. Default: full 64-char SHA-256 hash
* Required fields

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"]

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

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.

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)