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

@indiciopbc/proven-id-lib

v0.1.3

Published

Proven ID Library — server-side credential verification orchestration.

Readme

@indiciopbc/proven-id-lib

Server-side, framework-agnostic library for integrating Proven ID credential verification into any Node backend. It handles:

  • A WebSocket server that pushes verification state to the browser (paired with @indiciopbc/proven-id-js on the frontend)
  • Outbound calls to the Proven Agent (OOB invitation creation, credential presentation requests)
  • Parsing and processing the Proven Agent's webhook callbacks — via handleWebhookPayload(), callable from any HTTP framework
  • Your business logic hook (validationRules) for deciding whether a presented credential grants access

For the conceptual walkthrough — system components, the full initialization/presentation sequence, WebSocket message schema, and webhook payload shapes — see SPEC.md. This README covers installing and calling the actual API.

Install

yarn add @indiciopbc/proven-id-lib

The core library has no framework dependency. If you use the bundled Express adapter (@indiciopbc/proven-id-lib/dist/express), express (^4.19.0) is a peer dependency — install it if your backend doesn't already have it. It's optional otherwise.

Quick start

import { ProvenIdLib } from "@indiciopbc/proven-id-lib";
import { createExpressWebhookRouter } from "@indiciopbc/proven-id-lib/dist/express";
import express from "express";

const provenLib = ProvenIdLib.init({
  websocket: { path: "/ws", sessionTimeoutMs: 5 * 60 * 1000 },
  credentialSchema: "<issuer-did>:2:<schema-name>:<schema-version>",
  credentialAttributes: ["employer_name"],
  validationRules: async (attributes) => {
    if (attributes.employer_name !== "Acme Corp") {
      return { status: "failed", code: "employer_mismatch" };
    }
    return { status: "success", code: "verification_success" };
  },
  provenAgent: { baseUrl: process.env.PROVEN_AGENT_BASE_URL! },
  provenApiKey: process.env.PROVEN_API_KEY!,
});

const app = express();
app.use(express.json());
app.use("/api/controller-webhook", createExpressWebhookRouter(provenLib.webhookDeps()));

const server = app.listen(3000);
provenLib.attachWebSocketServer(server);

Using a different framework

The Express adapter is a thin, optional convenience. Any framework can wire up the webhook endpoint directly against handleWebhookPayload(), which takes the already-parsed JSON body and returns { ok: boolean }:

// Fastify, Koa, Hono, raw http — anything that hands you a parsed body.
fastify.post("/api/controller-webhook", async (req, reply) => {
  const result = await provenLib.handleWebhookPayload(req.body);
  reply.status(200).send(result);
});

See config.sample.ts for every ProvenIdConfig field with inline documentation, and backend/src/provenIdConfig.ts / backend/src/server.ts in this repo for a complete, real-world wiring example (including graceful shutdown via provenLib.close()).

Note the setup ordering from SPEC.md §3: provenApiKey only exists once you've configured the webhook URL in the Proven Agent UI and generated a key there, so the everything-else-first / provenApiKey-last sequence in that section is expected, not a chicken-and-egg bug.

ProvenIdLib API

| Method | Description | |---|---| | ProvenIdLib.init(config: ProvenIdConfig) | Validates config (throws ConfigValidationError on bad input) and returns a ProvenIdLib instance. | | .handleWebhookPayload(payload: unknown): Promise<{ ok: boolean }> | Framework-agnostic entry point — pass it the already-parsed JSON body from any HTTP framework's webhook route. | | .webhookDeps(): WebhookHandlerDeps | Exposes the internal deps bundle for framework adapters, e.g. createExpressWebhookRouter(provenLib.webhookDeps()). | | .attachWebSocketServer(httpServer: http.Server) | Attaches the WebSocket server to an existing HTTP server at config.websocket.path. Call once, after app.listen(). | | .close(): Promise<void> | Closes all open WebSocket sessions and the WebSocket server. Call during shutdown. |

createExpressWebhookRouter(deps: WebhookHandlerDeps): express.Router, from @indiciopbc/proven-id-lib/dist/express, builds an Express router around handleWebhookPayload. It's the only place in the package that imports express.

Writing validationRules

type ValidationRulesFn = (
  attributes: Record<string, string>,
) => ValidationResult | Promise<ValidationResult>;

interface ValidationResult {
  status: "success" | "failed" | "error";
  code: string;
  message?: string;
  redirectUrl?: string;
}

This is called only on a successful, cryptographically-verified presentation (verification_status: "success") — it's where your business logic decides whether the presented attributes are actually sufficient (e.g. checking Nationality against an allowlist), not where you check whether the credential itself is valid. A failed or errored presentation never reaches validationRules; see onVerificationEvent below for those.

redirectUrl (if returned) takes precedence over message in the frontend UI. If omitted on success, the library falls back to config.redirectUrl, if set.

Real example, from this repo's demo backend (backend/src/provenIdConfig.ts):

function buildValidationRules(app: AppConfig, pool: Pool): ValidationRulesFn {
  return async (attributes) => {
    if (attributes.Nationality && attributes.Nationality !== app.identity.expectedNationality) {
      return { status: "failed", code: "nationality_mismatch", message: "Nationality does not match the expected nationality." };
    }
    const { rows } = await pool.query(
      `INSERT INTO verification_log (schema_id, attributes, verification_status, result_code)
       VALUES ($1, $2, 'success', 'verification_success') RETURNING id`,
      [app.proven.credentialSchema, attributes],
    );
    const sessionId = randomUUID();
    await pool.query(
      `INSERT INTO sessions (id, verification_log_id, expires_at) VALUES ($1, $2, now() + ($3 || ' minutes')::interval)`,
      [sessionId, rows[0].id, app.session.durationMinutes],
    );
    return { status: "success", code: "verification_success", redirectUrl: `/api/session/exchange?token=${sessionId}` };
  };
}

onVerificationEvent

type VerificationEventHandler = (event: VerificationEvent) => void | Promise<void>;

Optional. Fires for every outcome where validationRules is not invoked — a failed verification (bad signature/schema compliance) or an error/user-rejection. Use it for audit logging; per SPEC.md §4.3, a failed verification should be treated as potentially fraudulent, so don't trust event.attributes in this handler for anything beyond logging.

VerificationEvent includes sessionId, invitationId, schemaId, attributes, verificationStatus ("success" | "failed"), resultStatus ("success" | "failed" | "error"), code, message, error, and receivedAt.

Webhook endpoint

Wire handleWebhookPayload() (directly, or via createExpressWebhookRouter()) at the path you register as the Proven Agent's webhook URL (SPEC.md §3.2). It accepts both webhook payload shapes described in SPEC.md §6 (connection-established and presentation-result). Whatever framework you use, make sure the JSON body is already parsed before it reaches this call (e.g. express.json() upstream in the middleware chain).

Webhook authentication

SPEC.md §6 states the Proven Agent authenticates its webhook calls with an API key in an HTTP header. Set config.webhookAuth = { headerName, apiKey } to require that header on every inbound webhook request — requests missing it or presenting the wrong value are rejected ({ ok: false, unauthorized: true }, surfaced as 401 by createExpressWebhookRouter) before the payload is processed. If you call handleWebhookPayload()/processWebhookEvent() directly instead of through the Express adapter, pass the incoming request's headers as the third argument. webhookAuth is optional — omit it and no check is performed (useful for local dev before you've confirmed the header name against your live agent).

WebSocket protocol

The outbound message types (invitation, processing, result, session_expired) and their fields are specified in SPEC.md §5@indiciopbc/proven-id-js already implements the client side of this protocol, so you only need it if you're writing a custom frontend client.

Errors

  • ConfigValidationError — thrown synchronously from ProvenIdLib.init() when required config fields are missing or malformed.
  • ProvenAgentError — thrown by the internal Proven Agent HTTP client on non-2xx responses after retries are exhausted; surfaces as an error-status result message to the connected client rather than an unhandled rejection.

See also