unshared-clientjs-sdk
v2.0.2
Published
Server-side Node.js SDK for the Unshared Labs V2 API
Readme
unshared-clientjs-sdk
Server-side Node.js SDK for Unshared — detect account sharing, analyze user events for fraud, and run email verification flows.
Install
npm install unshared-clientjs-sdkRequires Node.js 18+
Quick Start
import { UnsharedClient } from 'unshared-clientjs-sdk';
const client = new UnsharedClient({
apiKey: process.env.UNSHARED_API_KEY, // usk_…
});Methods
processUserEvent(params)
Record a user event and get a fraud signal back. Call this on login, signup, or any high-value action.
const result = await client.processUserEvent({
eventType: 'login',
userId: 'user_123',
emailAddress: '[email protected]',
deviceId: 'device_abc',
sessionHash: 'session_xyz',
ipAddress: '1.2.3.4', // plaintext — not encrypted
userAgent: req.headers['user-agent'],
});
if (result.success && result.data?.analysis.is_user_flagged) {
// Block or challenge the user
}Fields encrypted before sending: emailAddress, deviceId
checkUser(emailAddress, deviceId)
Quick check to see if a user is flagged. Useful in middleware or route guards.
const result = await client.checkUser('[email protected]', 'device_abc');
if (result.data?.is_user_flagged) {
// Deny access
}Safe default: Returns
{ is_user_flagged: false }on any failure (network error, outage). A backend outage will never accidentally block a legitimate user.
triggerEmailVerification(emailAddress, deviceId)
Send a 6-digit verification code to the user's email.
await client.triggerEmailVerification('[email protected]', 'device_abc');verify(emailAddress, deviceId, code)
Validate the code the user submitted.
const result = await client.verify('[email protected]', 'device_abc', '123456');
if (!result.success) {
if (result.error?.code === 'VERIFICATION_FAILED') {
// Wrong or expired code — ask user to retry
} else {
// Transport error (DELIVERY_FAILED) — retry or show generic error
}
} else {
// Verified — success: true means the code was correct
}submitFingerprintEvent(fingerprint, opts?)
Submit a browser fingerprint collected by unshared-frontend-sdk. Typically called by the middleware — you usually won't call this directly.
await client.submitFingerprintEvent(fingerprint, {
userId: 'user_123',
sessionHash: 'session_xyz',
eventType: 'page_view',
});Protection Middleware (Recommended)
unsharedBoundToUser is the full-featured middleware: auto-injects the fingerprint script, enforces verdicts, handles email verification flows, and dispatches events.
import { UnsharedClient, unsharedBoundToUser, flaggedResponse } from 'unshared-clientjs-sdk';
app.set('trust proxy', 1);
app.use(express.json());
const client = new UnsharedClient({ apiKey: process.env.UNSHARED_API_KEY! });
app.use(unsharedBoundToUser(client, {
userId: (req) => req.cookies?.userId,
emailAddress: (req) => req.cookies?.email,
includePathPrefix: ['/api/'],
onFlagged: ({ emailAddress, res }) => {
res.status(403).json(flaggedResponse(emailAddress));
},
}));Smoke test: curl http://localhost:3000/__unshared/status — returns { "status": "anonymous" | "ok" | "flagged" }.
Key options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| userId | (req) => string \| undefined | — | Required. Resolve the current user's ID |
| emailAddress | (req) => string \| undefined | — | Resolve the current user's email |
| routePrefix | string | "/__unshared" | Route mount prefix |
| includePathPrefix | string[] | — | Only these path prefixes trigger verdicts and events |
| onFlagged | (ctx) => void | — | Called when a flagged user makes a request |
| disableBotFilter | boolean | false | Skip bot UA filter (enable for E2E testing) |
| checkUserTimeoutMs | number | 500 | Timeout (ms) for checkUser API calls; fails open on timeout |
| skipPaths | string[] | — | Paths to skip entirely (static assets, health checks) |
| corsOrigins | string \| string[] | — | Allowed CORS origins; handles OPTIONS preflight |
| onError | (error, ctx) => void | — | Called on background SDK errors for observability |
See quickstart and flag semantics for full setup and testing details.
Simple Fingerprint Middleware
createUnsharedMiddleware is a lightweight alternative that only proxies fingerprint events — no verdicts, no script injection, no verification flows. Use unsharedBoundToUser unless you have a specific reason not to.
import { createUnsharedMiddleware } from 'unshared-clientjs-sdk';
app.use(express.json());
app.use(createUnsharedMiddleware(client, {
userIdExtractor: (req) => req.user?.id,
}));| Option | Type | Default | Description |
|--------|------|---------|-------------|
| userIdExtractor | (req) => string \| undefined | — | Pull user ID from your auth session |
| eventTypeExtractor | (req) => string \| undefined | — | Override event type |
| sessionIdExtractor | (req) => string \| undefined | — | Override session ID |
| ipAddressExtractor | (req) => string \| undefined | — | Override IP address |
| defaultEventType | string | "browser_event" | Fallback event type |
| routePrefix | string | "/unshared" | Route mount prefix |
| corsOrigins | string \| string[] | — | Allowed CORS origins |
Note: This middleware uses a different default prefix (
/unshared) and route (submit-fingerprint-event) thanunsharedBoundToUser(/__unshared,submit-fp).
Configuration
new UnsharedClient({
apiKey: 'usk_…', // required
baseUrl: 'https://api.unshared.ai', // optional
timeout: 10_000, // optional, ms
maxRetries: 3, // optional
});Response shape
All methods return ApiResult<T>:
{
success: boolean;
data?: T;
error?: { code: string; message: string; retryAfter?: number };
status: number; // HTTP status code
}Security
All PII is encrypted with AES-256-GCM before leaving your server. The encryption key is derived from your API key with SHA-256. Your API key is sent as the X-API-Key header — never in a URL or browser context.
