Doorman 0.13. Install from npm and Hex, or run the source examples.
Durable first-party visitor identity from browser history, with optional AI-assisted matching and risk scoring.
Know the context behind a request: the browser, session, account, authenticated user or agent, and what is still uncertain. Keep PostHog or Mixpanel for analytics. Run Doorman on your own server with your existing Postgres or D1 database.
Docs · Complete integration · Playground · Measured limitations
import { createDoormanClient } from "@aarondovturkel/doorman-browser";
const doorman = createDoormanClient({
endpoint: "/api/visitor",
analytics: { posthog, mixpanel }, // Your initialized SDKs; optional.
});
await doorman.identify();
// After your application's login succeeds:
await doorman.identify({ userId: user.id, accountId: account.id });
await doorman.update({ plan: "team" });
// Optional: forwards to the configured SDKs. Do not also track this event directly.
await doorman.track("Project created");
// On logout:
await doorman.reset();Existing posthog.capture and mixpanel.track calls also receive safe browser/session/account context. You do not need to identify through both SDKs. Scores and guessed user IDs stay private. Call doorman.destroy() on teardown.
import { createDoorman } from "@aarondovturkel/doorman-adapters/node";
const doorman = createDoorman({
db, // A Postgres pool.
secret: process.env.DOORMAN_IDENTITY_SECRET!,
namespace: "my-app",
evaluator: false, // Add Jev later if you need scoring.
});
const result = await doorman.assess(request, {
auth: currentUser
? { userId: currentUser.id, accountId: currentAccount.id }
: undefined,
});
// Private: result.context, result.identity, result.properties
return result.response;currentUser and currentAccount come from your existing server authentication. Never trust browser JSON for them. The response contains only { visitorId, sessionId, isReturning }.
For Next.js/Vercel, use the same API from @aarondovturkel/doorman-adapters/vercel. For Cloudflare:
import { createDoorman } from "@aarondovturkel/doorman-adapters/cloudflare";
const doorman = createDoorman({
db: env.VISITORS,
ai: env.AI,
secret: env.DOORMAN_IDENTITY_SECRET,
namespace: "my-app",
crossDevice: true,
});
return doorman.handle(request);Workers AI runs typesafe/jev; no separate TypeSafe key is needed. Reuse the handler across requests. Doorman creates its tables automatically on first use. No migration commands are required.
For Elixir/Phoenix:
doorman = Doorman.new(
repo: MyApp.Repo,
secret: System.fetch_env!("DOORMAN_IDENTITY_SECRET"),
namespace: "my-app",
evaluator: [api_key: System.fetch_env!("JEV_API_KEY")],
cross_device: true
)
Doorman.handle(conn, doorman, %{auth: %{user_id: to_string(current_user.id)}})
# Private context and analytics properties are in conn.assigns.The native Elixir package is {:doorman_identity, "~> 0.13.0"}. The package initializes its own tables in your Ecto Postgres repo. Full Phoenix setup.
| Status | Evidence |
|---|---|
authenticated |
Your server verified the current user and actor. |
remembered |
This browser cookie previously accompanied a verified identity. |
inferred |
Browser or login history suggests a possible relationship. |
ambiguous |
Multiple retained identities used this browser. |
unknown |
There is not enough evidence. |
An account can contain several users or agent credentials, and one user can have several browsers. Cookie continuity proves neither a particular human nor permission. Missing-cookie and cross-device suggestions never silently merge analytics identities in the recommended flow. The lower-level visitor APIs retain configurable browser-ID recovery for applications that explicitly want it.
Optional Jev evaluation returns automation and suspicious-activity scores. With enough aggregate evidence, the unified flow also asks for human, assistant and script scores. An assistant can be legitimate; an unknown session remains unknown.
if (
result.identity?.riskStatus === "evaluated" &&
result.identity.risk.automation > 0.85
) {
// Your application may request extra verification.
}The threshold is illustrative. Doorman never blocks an application action or shows a CAPTCHA. Optional server middleware adds request-pattern evidence. Optional AbuseIPDB enrichment adds cached, budgeted network reputation without storing raw IPs or sending them to Jev. Configuration and data flow.
Risk evidence never enters identity questions. Jev timeouts or malformed output fall back cleanly, with unavailable status and zero fallback risk. This means “not assessed,” not “proven safe.” Database errors return a controlled response.
Classification and cross-device scores are experimental and uncalibrated. The public-data pilot did not reliably separate all humans and agents. Browser similarity cannot establish family relationships, exact human headcounts or agent brands. Tests and live results.
npm install @aarondovturkel/doorman-browser@^0.13.0 @aarondovturkel/doorman-adapters@^0.13.0
# Or use pnpm add / bun add with the same package names.Node/Fastify · Next.js · Cloudflare · Phoenix · Other languages
From source, with Node 22.12+ and pnpm 9.12:
pnpm install
pnpm typecheck
pnpm test
pnpm lintpnpm test:e2e checks real browser/analytics SDK behavior against local endpoints. pnpm test:elixir requires a local Postgres database. Provider calls in the regression suite are mocked. Published benchmark results distinguish fixtures, public datasets, live Jev calls and load tests.
One browser client → your endpoint → indexed history → deterministic comparison → optional Jev → your database and analytics.
A cookie handles the normal return visit. Missing-cookie lookup retrieves a bounded shortlist, compares recent history, and asks typed questions through Jev. Confirmed login feedback can support private cross-device suggestions. Associations expire and can be erased through the same server API.
Normal setup needs no model training, shared learning service or separate identity directory. Optional cross-device suggestions use confirmed login history with your existing Jev evaluator. Earlier experimental modules and dated results are documented only in the research archive.
Read the design, privacy, analytics, request activity, scoring and retention. MIT licensed.
