Two layers: clients collect, engines serve
Client SDKs (browser, iOS, Android, React Native) run in your app — they read device signals behind a fail-closed consent gate, seal the bundle, POST it to your endpoint, and return the server's typed verdict. Embeddable engines (Node, Go, Python, Java) mount inside your own backend and run the entire identification pipeline in-process — unseal, consent gate, signal processing, typed verdict, visitorId, consent ledger, and the GDPR/DPDP rights API — with no separate server to deploy.
Client SDKs
The client SDKs collect device signals under a fail-closed consent gate, seal the payload (ECDH-P256 + HKDF-SHA256 + AES-256-GCM), POST the sealed bundle to your ingest endpoint, and return the server's authoritative typed result. identify() always resolves and never throws — denied consent, a missing engine, or a transport failure all surface as a typed reason, not an exception. The visitorId is server-generated; the client never fabricates one. All client SDKs are MIT-licensed.
@noidme/glassprint
ESM + CommonJS + TypeScript types. Ships a sub-30 KB gzipped bundle. Collects nine signal axes (canvas, WebGL, AudioContext, UA-CH, display, fonts, MEMS, math-FPU, WebGPU). The server adds the network tier (JA4 / TLS / HTTP2) invisible to JS. Requires a createSealedTransport to deliver to your ingest endpoint — without one, identify() returns reason: 'transport-error' and visitorId: null.
npm i @noidme/glassprint
import { createGlassprint, createSealedTransport } from '@noidme/glassprint';
const transport = createSealedTransport({
key, // AES-256-GCM CryptoKey (or fetch /_fp/pubkey for HPKE)
deliver: async (sealed) =>
(await fetch('https://id.acme.com/_fp/i/v1', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(sealed),
})).json(),
});
const gp = createGlassprint(
{ kid: 'acme', endpoint: 'https://id.acme.com/_fp/i/v1' },
transport,
);
// identify() always resolves — never throws.
const r = await gp.identify({ tag: 'checkout' });
if (r.reason === 'ok') {
r.class; // 'human' | 'declared_agent' | 'undeclared_automation' | 'unknown'
r.visitorId; // server-generated pseudonymous id (or null)
r.suspectScore; // additive risk score with security-fraud consent
}Source: github.com/noidme-git/glassprint · MIT
Glassprint (SwiftPM)
Requires iOS 15+ / macOS 12+. Zero third-party dependencies — Foundation (URLSession) + CryptoKit only. Collects OS-exposed signals (screen, hardware model, OS version, locale, CPU/memory). Canvas/WebGL/AudioContext signals are not collectable from native — the bundle is lower-entropy than the browser's by design; the server may return needsAttestation: true on a too-common fingerprint. identify() is async and never throws.
// Package.swift .package(url: "https://github.com/noidme-git/glassprint", from: "1.0.0") // target dependency: .product(name: "Glassprint", package: "glassprint") // Or: Xcode → File → Add Package Dependencies…
import Glassprint
let gp = Glassprint(
config: GlassprintConfig(
kid: "acme",
endpoint: URL(string: "https://id.acme.com/_fp/i/v1")!,
purposes: [.deviceIdentification]
),
consent: StaticConsentGate(granted: [.deviceIdentification])
)
let result = await gp.identify(IdentifyOptions(tag: "checkout"))
switch result.class {
case .human: print("human:", result.visitorId ?? "—")
case .declaredAgent: print("declared agent:", result.declaredAgentId ?? "—")
case .undeclaredAutomation: print("automation, suspect:", result.suspectScore ?? 0)
case .unknown: print("unknown:", result.error ?? "below confidence floor")
}Source: github.com/noidme-git/glassprint (ios/) · MIT
com.noidme:glassprint
minSdk 24 (Android 7.0). No third-party crypto or HTTP — javax.crypto / java.security + HttpURLConnection. Only runtime dep: kotlinx-coroutines. Collects Build.* signals, screen/display, locale — zero runtime permissions, no IMEI/ANDROID_ID/Advertising ID. Canvas/WebGL/AudioContext are not reachable from native; the bundle is lower-entropy by design. identify() is a suspend function and never throws.
// app/build.gradle.kts
dependencies {
implementation("com.noidme:glassprint:1.0.0")
}import com.noidme.glassprint.*
val gp = Glassprint.create(
context = applicationContext,
config = GlassprintConfig(
kid = "acme",
endpoint = "https://id.acme.com", // SDK appends /_fp/…
purposes = listOf(FpPurpose.DEVICE_IDENTIFICATION),
),
consent = ConsentProvider { purpose -> myConsentStore.isGranted(purpose) },
)
lifecycleScope.launch {
val result = gp.identify(IdentifyOptions(tag = "checkout"))
when (result.visitorClass) {
VisitorClass.HUMAN -> println("human: ${result.visitorId ?: "—"}")
VisitorClass.DECLARED_AGENT -> println("declared agent")
VisitorClass.UNDECLARED_AUTOMATION -> println("automation, suspect: ${result.suspectScore ?: 0.0}")
VisitorClass.UNKNOWN -> println("unknown: ${result.error ?: "below confidence floor"}")
}
}Source: github.com/noidme-git/glassprint (android/) · MIT
@noidme/glassprint-react-native
Same createGlassprint API and sealed-bundle wire contract as the browser client. Peer dependency: react-native-quick-crypto (bridges WebCrypto to iOS CryptoKit / Android javax.crypto). Without host-supplied deviceFacts, only Intl timezone and navigator.language are auto-detected — pass model, OS version, and screen geometry (e.g. from react-native-device-info) for full signal depth. Jailbreak/root/emulator detection is host-supplied; this SDK does not bundle a detector.
npm i @noidme/glassprint-react-native npm i react-native-quick-crypto # peer: native crypto bridge (CryptoKit / javax.crypto) cd ios && pod install # link the native crypto pod
// index.js — before any glassprint call
import { install } from 'react-native-quick-crypto';
install();
// ---
import { createGlassprint } from '@noidme/glassprint-react-native';
import { Platform, Dimensions, PixelRatio } from 'react-native';
const gp = createGlassprint(
{ kid: 'acme', endpoint: 'https://id.acme.com' },
{
deviceFacts: {
os: Platform.OS,
osVersion: String(Platform.Version),
screenWidth: Dimensions.get('window').width,
screenHeight: Dimensions.get('window').height,
pixelRatio: PixelRatio.get(),
},
},
);
const r = await gp.identify({ tag: 'checkout' });
r.class; // 'human' | 'declared_agent' | 'undeclared_automation' | 'unknown'
r.visitorId; // server-generated pseudonymous id (or null)Source: github.com/noidme-git/glassprint (react-native/) · MIT
@noidme/glassprint-protocol
The single source of truth for the sealed-bundle wire contract — the types that the client seals and the server opens. Node-native TypeScript, no build step. Exports OpenedBundle, SealedBundle, IngestResponse, vocabulary types (FpPurpose, VisitorClass, FpMode, IngestReason, SmartSignals), and a canonicalBytes(value) helper that produces byte-identical serialization on both client and server (the precondition for the GCM seal to round-trip). Import this package when you are building your own client or server adapter and need the exact wire types.
npm i @noidme/glassprint-protocol
import { canonicalBytes, type OpenedBundle } from '@noidme/glassprint-protocol';
// Shared wire types: OpenedBundle, SealedBundle, IngestResponse,
// FpPurpose, VisitorClass, FpMode, IngestReason, SmartSignals, …
const payload: OpenedBundle = { /* signals, manifest, receipt, nonce, clientTs */ };
const plaintext = canonicalBytes(payload); // byte-identical on client and serverSource: github.com/noidme-git/glassprint (protocol/) · MIT
Embeddable engines
The embeddable engines run the entire glassprint product in-process inside your own backend — no separate glassprint-server to deploy. Add one dependency, supply a master secret and a storage backend, and your backend immediately serves every glassprint endpoint: /_fp/pubkey, /_fp/i/v1 (ingest + full pipeline), /me (transparency / self-service erasure), and the full GDPR/DPDP privacy-rights API (/privacy/dsar, /privacy/erase, /privacy/contest). The pipeline: unseal → consent gate (GPC enforced) → replay/staleness → 9-probe + JA4 anti-spoof → 4-class verdict + confidence floor → salted-HMAC epoch-rotated tenant-bound visitorId → HMAC-hash-chained tamper-evident ledger.
@noidme/glassprint-engine
Express middleware or Fastify plugin. SQLite (Node built-in node:sqlite, zero npm deps) or Postgres (npm i pg) for durable storage. In-memory for dev/tests. OIDC/JWT authenticator included for the /privacy/* routes (createOidcAuthenticator()). Web Bot Auth (RFC 9421) verifier wired. Full programmatic Art-17 erasure and TTL retention GC via engine.erase() and engine.retentionGC().
npm i @noidme/glassprint-engine npm i express # or: npm i fastify
import express from 'express';
import { createEngine } from '@noidme/glassprint-engine';
import { glassprintEngine } from '@noidme/glassprint-engine/express';
// All endpoints auto-mounted — /_fp/pubkey, /_fp/i/v1, /me, /privacy/*, /healthz
const engine = await createEngine({ storage: { driver: 'sqlite', path: './glassprint.db' } });
express().use(glassprintEngine(engine)).listen(8080);github.com/noidme/glassprint-engine-go
Pure stdlib — net/http, crypto, database/sql. Zero third-party imports in the engine itself; bring your own SQL driver (pure-Go SQLite via modernc.org/sqlite or lib/pq for Postgres). Handler() returns a plain http.Handler mountable at root or under any prefix. The zero-value Config{} is a working dev setup — keys auto-generated and persisted.
go get github.com/noidme/glassprint-engine-go
import glassprint "github.com/noidme/glassprint-engine-go"
// Zero-config: in-memory storage, auto-generated keys, kid "default".
eng, err := glassprint.New(glassprint.Config{})
if err != nil {
log.Fatal(err)
}
// Handler() returns a plain http.Handler — mount at root or under a prefix.
http.Handle("/", eng.Handler())
log.Fatal(http.ListenAndServe(":8080", nil))glassprint-engine
Runtime dependency: cryptography>=41 (ECDH-P256 + HKDF + AES-GCM). Everything else — HMAC, SHA-256, canonical JSON, SQLite, HTTP — uses the Python stdlib. FastAPI (ASGI) and Flask (WSGI) adapters ship as optional extras. SQLite with WAL mode for single-process durable storage; implement the Storage protocol to bring Postgres or Redis. Env-var configuration: GLASSPRINT_MASTER_SECRET, GLASSPRINT_KIDS, GLASSPRINT_STORAGE. Python 3.11+; 3.9/3.10 with optional typing_extensions.
pip install "glassprint-engine[fastapi]" # + FastAPI/Starlette ASGI adapter # pip install "glassprint-engine[flask]" # + Flask WSGI blueprint
from fastapi import FastAPI import glassprint_engine as gp # Keys auto-generated and persisted; all endpoints auto-mounted. engine = gp.create(storage="sqlite:///glassprint.db") app = FastAPI() app.include_router(gp.asgi.router(engine)) # Run: uvicorn main:app
com.noidme:glassprint-engine
Two artifacts: the framework-agnostic glassprint-engine core (Javalin, Vert.x, Micronaut, plain HttpServer) and a glassprint-engine-spring-boot-starter that auto-registers every bean and controller via Spring Boot 3 @AutoConfiguration. Storage: in-memory (zero config) + JDBC adapter (H2 / Postgres, portable ANSI-SQL schema). Add two application.properties lines and every glassprint endpoint is live. Framework-agnostic programmatic use also supported via GlassprintEngine.create(config) + GlassprintEndpoints.
<!-- Spring Boot 3 starter — zero-wiring auto-configuration --> <dependency> <groupId>com.noidme</groupId> <artifactId>glassprint-engine-spring-boot-starter</artifactId> <version>0.1.0</version> </dependency>
# application.properties # Add the starter + these two lines — every glassprint endpoint is live. glassprint.master-secret=your-at-least-32-char-production-secret glassprint.kids=tenant-a # optional: glassprint.jdbc-url=jdbc:postgresql://localhost/glassprint
deviceId (cross-browser stable device ID) field is reserved and returns null in all engines today. smartSignals.incognito is a placeholder (false) pending additional probes. Self-host ships today; the managed-SaaS option is design-partner onboarding, not open self-signup.Self-host the engine, own your signals
Mount an embeddable engine in your own backend so no device intelligence leaves your perimeter, then point any client SDK at it.