Edge Function

Identity Resolver

738 LOC — cross-device identity resolution with domainprint aliasing, parallel queries, and Upstash caching.

Resolution Strategy

Fast Path

Known user (userId exists, not anon_)

1 query: _get_user_identity_cached()
         ├── Cache hit:  ~50ms
         └── Cache miss: ~150ms

3 cases:
A) domainprint == primary → no_change
B) domainprint ∈ aliases  → sync_to_primary
C) domainprint is NEW     → merge + alias

Parallel Path

Anonymous user — full search

3 queries in parallel (ThreadPoolExecutor):
├── _query_by_fingerprint (FP#hash)
├── _query_by_domainprint (DP#hash)
└── _query_by_behavioral  (BEH#hash)

~150ms total (vs ~450ms sequential)

Priority: FP > DP > Behavioral > New

Decision Tree

resolve_identity(fingerprint, domainprint, behavioral_fp, user_id)
│
├── user_id exists && !anon_?
│   │  YES → FAST PATH
│   │  ├── get cached user (1 query)
│   │  ├── user exists?
│   │  │   NO  → create_new_user() [confidence: 100]
│   │  │   YES → check domainprint:
│   │  │         ├── dp == primary    → "existing_identity" [100]
│   │  │         ├── dp ∈ aliases     → "alias_detected" [100]
│   │  │         └── dp is NEW        → "new_domainprint_merged" [95]
│   │
│   └── NO → PARALLEL PATH
│       ├── FP match? → "identified_by_fingerprint" [90]
│       ├── DP match? → "network_change" [95]
│       ├── BEH match? → "identified_by_behavioral" [~85]
│       └── No match → "new_anonymous_user" [60]

Domainprint Aliasing

A user's browser fingerprint can change (network change, VPN, update), but the domainprint — a first-party cookie unique to a domain — stays stable. When a new domainprint appears for a known user, it's merged as an alias of the primary.

User Profile (DynamoDB)
─────────────────────
PK: USER#user_2abc
SK: PROFILE
├── primaryDomainprint: "dp_A"    ← master
├── domainprintAliases: [
│     {
│       domainprint: "dp_B",
│       isPrimary: false,
│       status: "merged",
│       mergedInto: "dp_A",
│       mergedAt: "2026-03-15T...",
│       sessionCount: 12,
│       mergeContext: {
│         country: "ES",
│         asn: "3352",
│         userAgent: "Chrome/125..."
│       }
│     }
│   ]
├── identities: [
│     { fingerprint: "fp_1", domainprint: "dp_A", confidence: 100 },
│     { fingerprint: "fp_2", domainprint: "dp_B", confidence: 90 }
│   ]
└── totalDevices: 2

Reverse Indexes

DynamoDB doesn't support secondary lookups natively. We maintain 3 reverse indexes for O(1) lookups:

IndexPK PatternMaps ToTTL
FingerprintFP#{hash}userId + confidence (90)30 days
DomainprintDP#{hash}userId + isAlias flag365 days
BehavioralBEH#{hash}userId + similarity (85)30 days

Confidence Scoring

100%

User ID + DP

95%

DP match / merge

90%

FP match

60%

New anonymous

Cache Strategy (Upstash Redis)

_get_user_identity_cached(user_id):
  │
  ├── cache_get("user:{user_id}")
  │     ├── HIT → parse JSON, validate
  │     │        ├── Corruption check: primary ∈ aliases? → invalidate
  │     │        └── Return cached user (~50ms)
  │     │
  │     └── MISS → _get_user_identity(user_id)
  │               ├── DynamoDB GetItem (~150ms)
  │               ├── cache_set(key, json, TTL=300s)
  │               └── Return user
  │
  └── Cache invalidation:
      └── On primaryDomainprint fix → cache_delete("user:{id}")

Performance Optimizations

ThreadPoolExecutor global

Pool de 3 workers reutilizado entre invocaciones Lambda — ahorra ~15ms por request

Fire-and-forget timestamps

_update_identity_timestamp_async() — Thread daemon, no bloquea response (~100ms ahorro)

Identity eliminada de response

Los handlers no consumen identity completa — reducida payload en match responses (~80ms)