Skip to content

商业化投放接入

把指纹用于广告归因 / 转化回收 / 匿名访客识别有一套行业实践。本页给出 Tripo 内部推荐的接入方式。

强监管领域

浏览器指纹属于个人信息处理,业务侧需在隐私政策中明示告知用途与保留期,符合:

  • 中国《个人信息保护法》《数据安全法》《互联网广告管理办法》
  • 欧盟 GDPR + ePrivacy(首次访问需 Cookie Banner 同意)
  • 加州 CCPA/CPRA

合规是业务侧职责,不是库的职责。

一、不要只靠指纹

工业实践中,指纹只是多信号之一:

  • 一方 ID(登录态)业务侧最强信号,能跨设备打通
  • Click ID(gclid / fbclid / bd_vid / ocid)平台官方归因 ID,从落地页 URL 取,必收
  • 服务端 IP / UA / TLS / HTTP/2 指纹网关日志记录,防伪 + 补全
  • 浏览器指纹匿名用户去重、跨会话识别 —— 这是本库的位置
  • 一方 Cookie / localStorage短期会话续接,配合服务端 Set-Cookie

只有指纹没有 click_id,平台那边对不上账。

二、推荐架构

┌─────────────┐                      ┌────────────┐
│ 落地页加载  │                      │ 业务后端   │
│             │                      │            │
│ 1. 读 click │  POST /track         │ - 盐值哈希 │
│    _id      │ ───────────────────► │   入库     │
│             │  { click_id, vid,    │            │
│ 2. 计算 vid │    utm, event }      │ - 90 天    │
│   (兜底)  │                      │   TTL      │
│             │                      │            │
│ 3. sendBea- │                      │            │
│    con 上报 │                      └────┬───────┘
└─────────────┘                           │
                                          │ 转化时

                              ┌─────────────────────────┐
                              │ 各平台 Conversion API   │
                              │ - Meta CAPI             │
                              │ - Google Enhanced Conv. │
                              │ - 巨量引擎转化 API       │
                              │                         │
                              │ 传 click_id + 转化事件  │
                              │ 不传指纹本身            │
                              └─────────────────────────┘

三、前端落地代码

ts
import FP from '@tripo3d/fingerprint';

interface TrackPayload {
  vid: string;
  click_id: string | null;
  utm: Record<string, string>;
  event: 'page_view' | 'sign_up' | 'purchase';
  ts: number;
}

async function getOrCreateVid(): Promise<string> {
  // 优先 localStorage 兜底(Safari ITP 7 天后可能清,需要服务端 Set-Cookie 双写)
  let vid = localStorage.getItem('vid');
  if (vid) return vid;

  const fp = await FP.load();
  const { visitorId } = await fp.get();
  vid = visitorId;
  localStorage.setItem('vid', vid);
  document.cookie = `vid=${vid}; max-age=63072000; path=/; SameSite=Lax; Secure`;
  return vid;
}

function readClickId(): string | null {
  const params = new URLSearchParams(location.search);
  return params.get('gclid') ?? params.get('fbclid') ?? params.get('bd_vid') ?? null;
}

function readUtm(): Record<string, string> {
  const params = new URLSearchParams(location.search);
  const utm: Record<string, string> = {};
  for (const k of ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']) {
    const v = params.get(k);
    if (v) utm[k] = v;
  }
  return utm;
}

export async function track(event: TrackPayload['event']) {
  const vid = await getOrCreateVid();
  const payload: TrackPayload = {
    vid,
    click_id: readClickId(),
    utm: readUtm(),
    event,
    ts: Date.now(),
  };
  // sendBeacon 即使页面关闭也能发出去
  navigator.sendBeacon('/track', JSON.stringify(payload));
}

用 requestIdleCallback 触发,别卡首屏

首次 PV 上报建议放到 requestIdleCallback(库内部 load() 已经做了等待)。高频事件(PV)只发 vid,转化事件再带完整 components。

四、服务端要做的事

步骤做法
入库前哈希SHA-256(visitor_id + server_secret),原始指纹不落库
TTL90 天滚动,过期重算
归并任务T+1 离线把同设备多次访问归并到一个 device_id
转化回传通过各平台 Conversion API(Meta CAPI / Google Enhanced Conv. / 巨量转化 API)回传 click_id + 转化事件 + hashed_email/phone不传指纹本身

五、CSP 兜底

为了防止后续升级版本时被悄悄塞回任何外部请求,建议在站点 CSP 里收紧 connect-src

Content-Security-Policy:
  connect-src 'self' <你的上报域名> <Conversion API 域名>;

m1.openfpcdn.io(上游遥测域名)不在白名单里就一定发不出去。

六、稳定性 vs 熵的取舍

广告归因里**「同一个人识别成两个」比「两个人撞成一个」代价大得多**——前者会让平台高估 CPA,烧广告费。所以本库已经:

  1. 在 Safari 17+ / Firefox 120+ 反指纹模式下走稳定化分支(牺牲熵换稳定)
  2. 移动端 confidence.score 主动降到 0.3-0.5,提示业务侧不要过度依赖

如果你的场景对稳定性更敏感,建议把 userAgent 完整串、fonts 这类高频变化的字段从 hash 里剔除,只保留 Canvas/WebGL/Audio/Screen 等强稳定信号:

ts
import { hashComponents, load } from '@tripo3d/fingerprint';

const fp = await load();
const { components } = await fp.get();

const stable = {
  canvas: components.canvas,
  webgl: components.webgl,
  audio: components.audio,
  screenResolution: components.screenResolution,
  timezone: components.timezone,
  platform: components.platform,
};

const stableId = hashComponents(stable);

七、合规清单

业务对接前请确认:

  • [ ] 隐私政策写明采集设备特征用于广告归因、保存期限、第三方共享范围
  • [ ] 首次访问弹同意框,未同意前只发匿名 PV,不算指纹
  • [ ] 提供撤回同意 + 删除数据的入口
  • [ ] 与广告平台签 DPA
  • [ ] 入库做盐值哈希,不存原始 components

下一步

基于 MIT 协议发布(内部使用)