A privacy-first device-signal collector. It enumerates device/browser signals, normalises them into one canonical, versioned schema, and sends them to an endpoint that mints a NUID (network/device unique identifier). The collector gathers and sends; your backend owns identity. It never tracks across sites.
⚠️ Consent matters. Device fingerprinting commonly requires user consent under UK/EU ePrivacy & GDPR (and similar regimes). This package is consent-gated by default — nothing is collected or sent until you grant consent — and high-entropy signals are opt-in and suppressed under Global Privacy Control / Do-Not-Track. This is not legal advice; confirm your obligations.
TypeScript-first, zero runtime dependencies, SSR-safe, with reference native SDKs.
import { createCollector } from '@pametan/device-nuid';
const collector = createCollector({ endpoint: 'https://api.example.com/nuid' });
collector.grantConsent(); // after your consent UX
const { response } = (await collector.send())!; // response is your endpoint's reply (the NUID)Device intelligence powers fraud and risk decisions, but the signals are
scattered across inconsistent, often-deprecated browser APIs, and the strongest
ones carry real consent weight. device-nuid gathers them through one clean,
consent-gated interface, normalises them into a single schema that the web
collector and the native iOS/Android SDKs all produce, and POSTs that to your
endpoint. You compute the NUID (e.g. by hashing the canonical payload); the
package never mints an id itself.
npm install @pametan/device-nuidRequires Node 24+ to build; runs in any modern browser. Ships ESM + types.
import { createCollector } from '@pametan/device-nuid';
const collector = createCollector({
endpoint: 'https://api.example.com/nuid',
level: 'basic', // 'basic' (default) | 'extended'
requireConsent: true, // default
beacon: false, // true => fire-and-forget via sendBeacon (no response)
});
// after the user consents:
collector.grantConsent();
const payload = await collector.collect(); // the normalised payload (or null)
const result = await collector.send(); // POSTs; result.response is the NUID replysend() resolves with { payload, response, viaBeacon } — response is your
endpoint's parsed body (e.g. { nuid: "..." }). One-shot collect(config) and
send(config) helpers exist too.
requireConsentdefaults to true:collect()/send()returnnulluntilgrantConsent()is called (orconsent: trueis passed).level: 'extended'adds high-entropy signals (WebGL renderer, a canvas hash). They're off by default and are suppressed when the browser signals Global Privacy Control or Do-Not-Track (respectGpcDnt, default true).
Your endpoint receives the JSON payload and is expected to return the minted id,
e.g. { "nuid": "..." }. That response is surfaced as result.response.
Basic (default): device type, OS (+ version), browser (+ version/engine), languages, time zone (IANA + offset), screen (size, available, colour depth, pixel ratio, orientation), viewport, hardware (cores, memory), capabilities (touch, pointer, cookies, DNT/GPC), network hint, UA Client Hints and the raw user-agent. OS/browser/device-type come from UA Client Hints where available, falling back to user-agent parsing.
Extended (opt-in): WebGL vendor/renderer and a canvas hash.
See schema/nuid-payload.schema.json for the
full, versioned contract. canonicalize(payload) gives a stable, sorted-key JSON
string so client and server hash identical bytes.
| Export | Description |
|---|---|
createCollector(config) |
A collector: collect, send, grant/revoke/hasConsent. |
collect(config) / send(config) |
One-shot equivalents. |
canonicalize(value) |
Deterministic JSON for stable hashing. |
validatePayload(payload) |
Light structural check → list of problems. |
SCHEMA_VERSION, SDK_VERSION |
Version constants. |
Types NuidPayload, DeviceType, CollectionLevel, CollectorConfig,
SendResult, CollectEnv are exported. Pass env to inject the environment
(SSR / testing).
Reference implementations targeting the same schema (collector.platform
ios/android, deviceType: 'mobile-app') live under sdk/:
sdk/ios/DeviceNuid.swift and
sdk/android/DeviceNuid.kt. They are reviewed
skeletons — build and test them in Xcode / your Android project; they are not
compiled in this repo's CI, and they keep the same consent gate.
In a non-browser environment collect() returns null and send() no-ops — no
window/navigator access — so it's safe to import in SSR/Node code paths.
npm install
npm run typecheck
npm test # normalisation, consent, GPC/DNT, transport, SSR, schema
npm run build # emit dist/An engineering aid, not legal advice. You are responsible for obtaining valid
consent and meeting your privacy obligations. MIT licensed — see
LICENSE.
We're Pametan — a specialist fintech/regtech engineering agency working across UK, US and Canadian rails (FCA · CFPB · FCAC). We build the production device-intelligence and fraud-signal pipelines this feeds into — consent-aware, audited, and tuned to your risk models.