stile
SDKs

Node.js SDK

A typed convenience wrapper around the Stile HTTP API for Node.js and TypeScript.

You can also use the HTTP API directly

The Node.js SDK is a thin convenience wrapper — every method maps 1:1 to an HTTP endpoint documented in the HTTP API reference. Use the HTTP API directly if you're working in Python, Ruby, Go, PHP, or any other language. For webhook signature verification in any language, see the verification guide.

Installation

npm install @stile/node

Initialization

Create a single client instance and reuse it throughout your application. Pass your API key as the first argument.

import Stile from "@stile/node";

const stile = new Stile(process.env.STILE_API_KEY!);

The SDK automatically retries failed requests and handles timeouts. Use vk_test_ keys in development and vk_live_ in production.

Server-only

The Node.js SDK is server-only. Do not use it in browser code.

Configuration

You can customize the base URL, retry count, and request timeout:

const stile = new Stile(process.env.STILE_API_KEY!, {
  baseUrl: "https://api.stile.dev",  // default
  maxRetries: 2,                      // default, retries on 5xx and network errors
  timeout: 30_000,                    // default, 30 seconds
});

Verification Sessions

Create a session

Pass a use_case when creating a session. The API resolves the required age tier internally based on the user's jurisdiction.

const session = await stile.verificationSessions.create({
  type: "age",
  use_case: "alcohol_delivery",
  return_url: "https://yourapp.com/verify/done",
  cancel_url: "https://yourapp.com/verify/cancel",
  client_reference_id: "user_123",
  // Optional: pass delivery address jurisdiction for VPN mismatch detection
  delivery_jurisdiction: "US-OR",
});

// session.id                  — e.g. "vks_abc123"
// session.client_secret       — pass to the frontend widget
// session.expires_at          — Unix timestamp (24h from now)
// session.status              — "created"
// session.ip_jurisdiction     — "US-CA" (detected from IP, if delivery_jurisdiction was set)
// session.jurisdiction_mismatch — true if delivery ≠ IP jurisdiction

Retrieve a session

const session = await stile.verificationSessions.retrieve("vks_abc123");

Cancel a session

const session = await stile.verificationSessions.cancel("vks_abc123");
// session.status === "cancelled"

List sessions

const { data, has_more } = await stile.verificationSessions.list({
  limit: 20,
  starting_after: "vks_xyz",
});

Webhook Endpoints

// Create
const endpoint = await stile.webhookEndpoints.create({
  url: "https://yourapp.com/api/webhooks",
  enabled_events: ["verification_session.verified", "verification_session.failed"],
  description: "Production webhook",
});
// endpoint.secret — save this to verify signatures

// Retrieve
const ep = await stile.webhookEndpoints.retrieve("we_abc123");

// Update
await stile.webhookEndpoints.update("we_abc123", {
  enabled_events: ["*"],
  status: "enabled",
});

// Delete
await stile.webhookEndpoints.del("we_abc123");

// List
const { data } = await stile.webhookEndpoints.list();

// List delivery attempts
const { data: deliveries } = await stile.webhookEndpoints.listDeliveries("we_abc123");

Events

// Retrieve a single event
const event = await stile.events.retrieve("evt_abc123");

// List events
const { data } = await stile.events.list({ limit: 20 });

Compliance Check

Check product-level compliance rules for one or more products in a jurisdiction. Returns per-product details and a most-restrictive merged summary. Useful for checking prohibited products or inspecting compliance details before creating a session.

// Check what's allowed in the user's jurisdiction
const compliance = await stile.compliance.check({
  use_cases: ["alcohol_delivery", "tobacco_nicotine"],
  jurisdiction: "US-CA",
});

if (compliance.most_restrictive.any_prohibited) {
  // Some products can't be sold here
  console.log("Prohibited:", compliance.most_restrictive.prohibited_use_cases);
  // Remove prohibited items from the cart or show an error
}

// Create a session — the API resolves the age tier internally
const session = await stile.verificationSessions.create({
  type: "age",
  use_case: "alcohol_delivery",
  client_reference_id: "order_456",
});

If you're using the widget SDK client-side, pass products directly and the widget handles the compliance check automatically. The server-side flow above is for backends that create sessions manually.

Verified Persons

Check if a user has been previously verified across any site in the stile network. Call this from your backend before loading the SDK to skip verification for returning users.

const result = await stile.verifiedPersons.lookup({
  email: "user@example.com",
  methods: ["document_capture"],
  min_strength: "document_capture",
  max_age: "30",
});

if (result.verified) {
  // User already verified — grant access, skip SDK
  console.log(result.verified_person_id, result.credentials);
} else {
  // Create a session and load the SDK
}

Webhook Signature Verification

Verify incoming webhook signatures to prevent processing spoofed events. The SDK provides two methods:

fromRequest() — for any Web API framework

Works with Next.js, Hono, Cloudflare Workers, Bun, Deno, and any framework using the standard Request object:

export async function POST(req: Request) {
  const event = await stile.webhooks.fromRequest(
    req,
    process.env.WEBHOOK_SECRET!,
  );

  if (event.type === "verification_session.verified") {
    console.log("Session verified:", event.data.id);
  }

  return Response.json({ received: true });
}

constructEvent() — low-level

For frameworks that don't use the standard Request object, pass the raw body and header directly:

const event = stile.webhooks.constructEvent(
  rawBody,                         // string or Buffer
  signatureHeader,                 // stile-signature header value
  process.env.WEBHOOK_SECRET!,
);

Always use raw body

JSON body parsers modify the request body before it reaches your handler, which invalidates the signature. Always pass the raw, unmodified request body.

Error handling

import {
  StileError,
  StileAuthenticationError,
  StileRateLimitError,
} from "@stile/node";

try {
  await stile.verificationSessions.create({ type: "identity" });
} catch (err) {
  if (err instanceof StileAuthenticationError) {
    // 401 — invalid or expired API key
  } else if (err instanceof StileRateLimitError) {
    // 429 — slow down, retry after err.retryAfter seconds
  } else if (err instanceof StileError) {
    console.error(err.type, err.code, err.message);
  }
}

For a complete guide to error types, retry strategies, and idempotency, see Error Handling.

On this page