npm package: @kwik-id/sdk-node
Description: Server-side Node.js SDK for [KwikID](https://identity.kwiknkap.com) identity verification. Create verification sessions, retrieve results, and verify webhook signatures.
Latest version: 0.1.0

# @kwik-id/sdk-node

Server-side Node.js SDK for [KwikID](https://identity.kwiknkap.com) identity verification. Create verification sessions, retrieve results, and verify webhook signatures.

## Installation

```bash
npm install @kwik-id/sdk-node
# or
pnpm add @kwik-id/sdk-node
# or
yarn add @kwik-id/sdk-node
```

**Requirements:** Node.js 18+

## Quick Start

```ts
import { KwikIDNode } from "@kwik-id/sdk-node";

const kwikId = new KwikIDNode({
  apiKey: process.env.KWIKID_API_KEY!,
});

// 1. Create a session (returns a clientSecret for the frontend SDK)
const session = await kwikId.createSession({
  referenceId: "user_123",
  userName: "Jane Doe",
  userEmail: "jane@example.com",
});

// 2. Pass session.token to your frontend as the clientSecret
// The frontend SDK handles the verification flow

// 3. Later, retrieve the verification result
const result = await kwikId.getVerificationResult(session.jobId);
console.log(result.status); // "verified" | "rejected" | "pending"
console.log(result.scores); // { overall, liveness, document, faceMatch }
console.log(result.extractedData); // { firstName, lastName, dateOfBirth, ... }
```

## Configuration

```ts
const kwikId = new KwikIDNode({
  apiKey: "your-api-key", // Required — from KwikID dashboard at identity.kwiknkap.com
  baseUrl: "https://api.identity.kwiknkap.com", // Optional — defaults to production
  timeout: 30000, // Optional — request timeout in ms (default: 30s)
});
```

## API Reference

### Sessions

#### `createSession(options?)`

Creates a new verification session. Returns a `SessionToken` containing the `token` (client secret) to pass to the frontend SDK.

```ts
const session = await kwikId.createSession({
  referenceId: "user_123", // Optional — your internal user/order ID
  userName: "Jane Doe", // Optional — pre-fill user name
  userEmail: "jane@example.com", // Optional — pre-fill user email
  metadata: { plan: "premium" }, // Optional — arbitrary metadata
});

// session.sessionId — unique session identifier
// session.jobId     — verification job ID
// session.token     — client secret for frontend SDK
// session.expiresAt — ISO 8601 expiration timestamp
```

#### `getSession(sessionId)`

Retrieves session details.

```ts
const details = await kwikId.getSession(session.sessionId);
// details.isUsed    — whether the session has been consumed
// details.expiresAt — expiration timestamp
```

### Verifications

#### `listVerifications(options?)`

Lists verification jobs with pagination and filtering.

```ts
const list = await kwikId.listVerifications({
  status: "verified", // Filter by status
  referenceId: "user_123", // Filter by your reference ID
  page: 1, // Pagination
  limit: 20,
  sortBy: "createdAt", // "createdAt" | "updatedAt" | "status"
  sortOrder: "desc", // "asc" | "desc"
});

// list.verifications — array of VerificationSummary
// list.pagination    — { total, page, limit, totalPages }
```

#### `getVerification(id)`

Gets full details of a single verification.

```ts
const verification = await kwikId.getVerification(jobId);
// verification.status       — "pending" | "verified" | "rejected" | ...
// verification.documentType — "passport" | "id_card" | "drivers_license" | ...
// verification.userName
// verification.completedAt
```

#### `getVerificationByReference(referenceId)`

Looks up a verification by your reference ID.

```ts
const verification = await kwikId.getVerificationByReference("user_123");
```

#### `getVerificationResult(id)`

Gets the detailed result including scores and extracted document data.

```ts
const result = await kwikId.getVerificationResult(jobId);

// Scores (0–1 scale)
result.scores?.overall;    // Combined score
result.scores?.liveness;   // Liveness detection score
result.scores?.document;   // Document authenticity score
result.scores?.faceMatch;  // Face match score

// Extracted document data (from OCR)
result.extractedData?.firstName;
result.extractedData?.lastName;
result.extractedData?.dateOfBirth;  // YYYY-MM-DD
result.extractedData?.idNumber;     // Document number
result.extractedData?.expiryDate;   // YYYY-MM-DD
result.extractedData?.documentType;
```

### Webhooks

#### `verifyWebhookSignature(options)`

Verifies incoming webhook signatures to ensure authenticity. Webhooks are signed using HMAC-SHA256.

```ts
import { verifyWebhookSignature } from "@kwik-id/sdk-node";

// Express example
app.post("/webhooks/kwikid", (req, res) => {
  const result = verifyWebhookSignature({
    payload: JSON.stringify(req.body),
    signature: req.headers["kwik-signature"] as string,
    secret: process.env.KWIKID_WEBHOOK_SECRET!,
    toleranceSeconds: 300, // Optional — reject events older than 5min (default)
  });

  if (!result.valid) {
    console.error("Invalid webhook:", result.error);
    return res.status(400).send("Invalid signature");
  }

  const { event } = result;

  switch (event.event) {
    case "verification.completed":
      console.log("Verified:", event.data.jobId);
      console.log("Scores:", event.data.scores);
      console.log("Extracted:", event.data.extractedData);
      break;
    case "verification.rejected":
      console.log("Rejected:", event.data.jobId);
      break;
    case "verification.failed":
      console.log("Failed:", event.data.errorMessage);
      break;
  }

  res.sendStatus(200);
});
```

### Webhook Events

| Event | Description |
|---|---|
| `verification.started` | User began the verification flow |
| `verification.uploaded` | Documents and selfie uploaded |
| `verification.completed` | Verification passed |
| `verification.rejected` | Verification rejected (e.g., document mismatch) |
| `verification.failed` | Verification failed (e.g., processing error) |

### Webhook Payload

```ts
{
  event: "verification.completed",   // WebhookEventType
  timestamp: "2024-01-15T10:30:00.000Z",
  data: {
    jobId: "job_abc123",
    referenceId: "user_123",          // your reference ID (if provided at session creation)
    status: "verified",
    environment: "live",              // "live" | "test"

    // Scores — present on completed/rejected events
    scores?: {
      documentScore?: number;         // 0–1
      documentBackScore?: number;     // 0–1 (if back was captured)
      faceMatchScore?: number;        // 0–1
      livenessScore?: number;         // 0–1
    },

    // Extracted OCR data — present on completed events
    extractedData?: {
      firstName?: string;
      lastName?: string;
      fullName?: string;
      dateOfBirth?: string;           // YYYY-MM-DD
      documentNumber?: string;
      uniqueIdentifier?: string;
      nationality?: string;
      expiryDate?: string;            // YYYY-MM-DD
      issuanceDate?: string;          // YYYY-MM-DD
      documentType?: string;
    },

    // Error info — present on failed events
    errorCode?: string;
    errorMessage?: string;

    metadata?: Record<string, unknown>;
  }
}
```

## Framework Examples

### Express

```ts
import express from "express";
import { KwikIDNode } from "@kwik-id/sdk-node";

const app = express();
const kwikId = new KwikIDNode({ apiKey: process.env.KWIKID_API_KEY! });

// Create a session for the frontend
app.post("/api/create-session", async (req, res) => {
  const session = await kwikId.createSession({
    referenceId: req.user.id,
    userName: req.user.name,
  });
  res.json({ clientSecret: session.token });
});
```

### Next.js (App Router)

```ts
// app/api/verification/session/route.ts
import { KwikIDNode } from "@kwik-id/sdk-node";
import { NextResponse } from "next/server";

const kwikId = new KwikIDNode({ apiKey: process.env.KWIKID_API_KEY! });

export async function POST(request: Request) {
  const { userId, userName } = await request.json();
  const session = await kwikId.createSession({
    referenceId: userId,
    userName,
  });
  return NextResponse.json({ clientSecret: session.token });
}
```

### NestJS

```ts
import { Injectable } from "@nestjs/common";
import { KwikIDNode } from "@kwik-id/sdk-node";

@Injectable()
export class VerificationService {
  private kwikId = new KwikIDNode({ apiKey: process.env.KWIKID_API_KEY! });

  async createSession(userId: string, userName: string) {
    return this.kwikId.createSession({ referenceId: userId, userName });
  }

  async getResult(jobId: string) {
    return this.kwikId.getVerificationResult(jobId);
  }
}
```

## Error Handling

All methods throw an `Error` with a descriptive message on failure:

```ts
try {
  const result = await kwikId.getVerification("invalid-id");
} catch (error) {
  // "KwikIDNode: GET /api/v1/verifications/invalid-id failed — Not found"
  console.error(error.message);
}
```

## License

MIT