Self-hosting
The entire stack runs from a single docker compose file: the identification API, the web app, Postgres for visitor storage, and Redis for rate limiting and caching. One command boots all four.
Boot the stack
docker compose upOnce the containers are healthy:
- API on
http://localhost:8080 - Web app and dashboard on their compose-mapped ports
- Demo keys seeded automatically: public
vp_pk_demo_public_key, secretvp_sk_demo_secret_key, dashboard tokenveriprint-dashboard-demo
Verify the API is up with the seeded public key:
curl -X POST http://localhost:8080/v1/identify \
-H "x-api-key: vp_pk_demo_public_key" \
-H "content-type: application/json" \
-d '{"signals":{"timezone":"Europe/Berlin","platform":"MacIntel"}}'Architecture
Four services, one network:
| Service | Role |
|---|---|
api | The identification engine. Serves POST /v1/identify and GET /v1/events/:requestId on port 8080, runs similarity matching, and writes events. |
web | The Next.js site and dashboard: event stream, API key management, docs. |
postgres | Durable storage: visitor clusters (signal vectors, weights, timestamps), identification events, and API keys. |
redis | Per-key rate limit counters and hot-path caching in front of Postgres. |
Environment variables
The compose file ships working defaults for local development. Override these for any real deployment:
| Variable | Purpose |
|---|---|
DATABASE_URL | Postgres connection string used by the API for visitors, events, and keys. |
REDIS_URL | Redis connection string used for rate limiting and caching. |
DEMO_PUBLIC_KEY | Public key seeded at boot (default vp_pk_demo_public_key). Set your own or disable seeding in production. |
DEMO_SECRET_KEY | Secret key seeded at boot (default vp_sk_demo_secret_key). |
RATE_LIMIT_MAX | Per-key request ceiling enforced via Redis. Exceeding it returns 429 with the limit echoed in the body. |
DATABASE_URL=postgres://veriprint:change-me@postgres:5432/veriprint
REDIS_URL=redis://redis:6379
DEMO_PUBLIC_KEY=vp_pk_your_public_key
DEMO_SECRET_KEY=vp_sk_your_secret_key
RATE_LIMIT_MAX=120Enabling server-side TLS fingerprinting
The strongest bot and spoof-resistance signal is the TLS handshake (JA4/JA3), which JavaScript cannot read or forge. The API does not compute it (Node terminates TLS before your code runs); instead it reads it from a header set by a fingerprinting reverse proxy in front of the API. Put something like fingerproxy or a JA4-capable edge (or Cloudflare Bot Management) ahead of the API so it sets one of x-ja4, cf-ja4, or x-http2-fingerprint. Veriprint folds the value in as the tlsFingerprint signal automatically. Override the header names it looks for with VERIPRINT_TLS_HEADERS (comma-separated). With no such proxy the signal is simply absent, and everything else works unchanged.
[email protected]), which is public knowledge from this documentation. The dashboard uses real email and password accounts with httpOnly session cookies. Put the API behind TLS, and point the browser SDK at your deployment with the endpoint option (or proxy /api/veriprint/v1/identify from your own origin, which is the SDK default and keeps identify traffic first-party).