npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@interview-sdk/server

v0.1.2

Published

Production-mode server-side evaluation, scoring, and signing for AI-powered interviews — keeps AI keys and score integrity off the client.

Readme

@interview-sdk/server

npm

Production-mode (Server Mode) evaluation, scoring, and signing. This is the security-critical package: AI keys never reach the browser, the score is computed against your own server-side question bank and rubric (never the client's copy), and it ships HMAC signing for both scores and webhooks.

Install

npm install @interview-sdk/core @interview-sdk/server

Also install an AI provider adapter (e.g. @interview-sdk/adapter-openai).

Runs on your infra

This package runs inside your backend — there is no maintainer-hosted service behind it. See the root Zero-Infra Guarantee.

Usage (Next.js Route Handler)

// app/api/interview/answer/route.ts
import { ServerAnswerProcessor, createInterviewAnswerHandler } from '@interview-sdk/server';
import { OpenAIAdapter } from '@interview-sdk/adapter-openai';

const processor = new ServerAnswerProcessor({
  questions: [
    {
      id: 'q1',
      prompt: 'Explain how hash maps handle collisions.',
      concepts: ['hashing', 'collisions'],
    },
  ],
  rubric: [{ id: 'technical', label: 'Technical depth', weight: 1 }],
  adapter: new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY! }),
  signingSecret: process.env.INTERVIEW_SIGNING_SECRET,
});

export const POST = createInterviewAnswerHandler(processor, {
  onError: (error) => console.error('interview answer processing failed', error),
});

This exact route, mounted at /api/interview/answer, is what @interview-sdk/react's <InterviewWidget mode="server"> POSTs to by default (see that package's ServerModeProcessor).

createInterviewAnswerHandler returns a standard (request: Request) => Promise<Response> (Fetch API), so it works as-is in any router that speaks that signature — Next.js Route Handlers, Deno, Bun, Cloudflare Workers, Hono. For Node's classic (req, res) style (plain http, Express), adapt it with a small shim:

import { createServer } from 'node:http';

const handler = createInterviewAnswerHandler(processor);

createServer(async (req, res) => {
  const chunks: Buffer[] = [];
  for await (const chunk of req) chunks.push(chunk as Buffer);
  const request = new Request(`http://localhost${req.url}`, {
    method: req.method,
    headers: req.headers as HeadersInit,
    body: Buffer.concat(chunks),
  });
  const response = await handler(request);
  res.writeHead(response.status, Object.fromEntries(response.headers));
  res.end(await response.text());
}).listen(3000);

Score integrity

ServerAnswerProcessor is constructed with your own canonical questions and rubric — the question/rubric fields in an incoming request are accepted (for wire compatibility with @interview-sdk/react) but never trusted for scoring. Only answer.questionId is used, to look up your configured question. A tampered client that strips a question's concepts or reweights the rubric in its own favor has no effect on the resulting score.

Pass signingSecret to have every returned evaluation HMAC-signed (evaluation.signature). This matters because @interview-sdk/react assembles the final report client-side from the per-turn evaluations it receives — a browser is not a trusted execution environment, so a sufficiently motivated user could edit that client-side state before it's displayed or submitted. If you need to prove a persisted/displayed report wasn't altered after your server produced it, re-verify each stored evaluation's signature with verify() before trusting it, rather than trusting whatever aggregate the client sends back.

import { verify } from '@interview-sdk/server';

const { signature, ...evaluation } = signedEvaluation;
const isAuthentic = verify(evaluation, signature, process.env.INTERVIEW_SIGNING_SECRET!);

Webhooks

import { WebhookDispatcher, verifyWebhookSignature } from '@interview-sdk/server';

const dispatcher = new WebhookDispatcher({
  url: 'https://your-app.example.com/webhooks/interview',
  secret: process.env.WEBHOOK_SECRET!,
});

await dispatcher.send('sessionEnd', { sessionId, totalScore });

Deliveries are HMAC-signed (Interview-Sdk-Signature header, Stripe/GitHub- style: t=<timestamp>,v1=<signature>) and carry an Interview-Sdk-Idempotency-Key header so your receiver can dedupe retried deliveries. Verify inbound webhooks with verifyWebhookSignature(rawBody, header, secret) — it also rejects payloads older than a tolerance window (5 minutes by default) to bound replay exposure.

WebhookDispatcher retries with exponential backoff (default 5 attempts) but keeps no state across process restarts — per the Zero-Infra Guarantee, this package holds no database of its own. If delivery must survive a restart, call dispatcher.send() from your own durable job queue rather than relying on its in-memory retry loop alone.

Testing

pnpm --filter @interview-sdk/server test
pnpm --filter @interview-sdk/server test:coverage

Not yet implemented

  • No authentication or rate-limiting: createInterviewAnswerHandler accepts and processes any request that reaches it, with no built-in caller identity check or request throttling. Every request results in a real, billed call to your AI provider — an unauthenticated, unthrottled route is a direct cost-based denial-of-service vector, not just a data-integrity concern. Put this handler behind your own auth middleware and a rate limiter (per-session or per-IP) before exposing it publicly; this package intentionally holds no auth/session store of its own (Zero-Infra Guarantee).
  • No built-in session-hijacking prevention: the processor trusts the request's previousTurns/currentFollowUpDepth/askedFollowUps fields as sent. For stronger guarantees, verify these against your own persisted session state (e.g. a signed session token) before calling processAnswer — this package intentionally holds no session store of its own (Zero-Infra Guarantee).
  • WebhookDispatcher's retry queue is in-process only, not durable across restarts (documented above).