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

@credenceid/verifier-sdk

v0.4.0

Published

TypeScript SDK for Credence ID verification orchestration

Readme

@credenceid/verifier-sdk

TypeScript SDK for the Credence ID Verification Orchestrator. Add identity verification to any web or Node.js application in minutes.

Installation

npm install @credenceid/verifier-sdk

Quick start (browser)

import { CredenceVerifier } from '@credenceid/verifier-sdk'

const verifier = new CredenceVerifier({ baseUrl: 'https://your-instance.credenceid.com' })

// 1. Activate with your license credentials
await verifier.activate({
  licenseKey: 'CS-your-license-key',
  profileId: 'your-profile-uuid',
})

// 2. Create a verification session
const session = await verifier.createVerificationSession({ purpose: 'age_check' })

// 3. Render the QR code into a container element (cross-device flow)
const qr = verifier.renderQRCode(session.qrImageBase64, '#qr-container')

// 3b. On the same device (e.g. mobile web), open the consumer page in a new tab instead.
//     Call this synchronously from inside the click handler — not after an `await` —
//     so the browser doesn't block it as a popup and `window.opener` gets set. That
//     `window.opener` link is what lets the consumer tab auto-return the user (and
//     post a completion message) when verification finishes, instead of falling back
//     to a manual "Return" button.
document.querySelector('#verify-on-this-device')?.addEventListener('click', () => {
  verifier.openVerificationTab(session)
})

// 4. Subscribe to the result (fires automatically when the user completes verification)
const unsubscribe = await verifier.subscribeToVerificationResult(session.sessionId, result => {
  console.log(result.status)    // 'verified' | 'denied' | 'failed' | 'expired' | 'cancelled'
  console.log(result.decision)  // 'verified' (when status === 'verified')
  qr.remove()      // remove QR from DOM
  unsubscribe()    // close SSE connection
})

// 5. Cancel if the user leaves (optional)
await verifier.cancelVerificationSession(session.sessionId)

Server-side (webhook) usage

For server-side integrations, use webhookUrl during activation. The Orchestrator will POST the VerificationResult to your endpoint whenever a session reaches a terminal state.

// Activate with a webhook URL
await verifier.activate({
  licenseKey: 'CS-your-license-key',
  profileId: 'your-profile-uuid',
  webhookUrl: 'https://your-server.com/webhooks/verification',
})

const session = await verifier.createVerificationSession({
  purpose: 'identity_check',
  referenceId: 'POS-TXN-00042',  // optional: your internal reference
})

Your webhook endpoint will receive a POST with the following body:

{
  "sessionId": "aaaabbbb-cccc-dddd-eeee-ffff00001111",
  "status": "verified",
  "purpose": "identity_check",
  "decision": "verified",
  "verifiedAt": 1716734400000
}

Webhook signature verification

Every webhook POST from the Orchestrator includes an X-Credence-Signature header containing an HMAC-SHA256 signature of the request body. Always verify this signature before processing the payload.

import { verifyWebhookSignature } from '@credenceid/verifier-sdk'

// Express.js example — use express.raw() to get the raw body buffer
app.post('/webhooks/verification', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['x-credence-signature'] as string
  const rawBody = req.body.toString('utf8')

  const isValid = await verifyWebhookSignature(
    rawBody,
    signature,
    process.env.CREDENCE_WEBHOOK_SECRET, // same value as JWT_SIGNING_SECRET on the server
  )

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' })
  }

  const result = JSON.parse(rawBody)
  // result.sessionId, result.status, result.decision, result.verifiedAt

  res.sendStatus(200)
})

verifyWebhookSignature() works in both browser (Web Crypto API) and Node.js environments. It uses constant-time comparison to prevent timing attacks.

Server-side integration patterns

There are three ways to receive a verification result. Choose based on your environment:

1. Browser — SSE (recommended for web POS UIs)

subscribeToVerificationResult() automatically uses SSE when window is defined. No extra configuration needed — this is the quick-start example above.

Latency: near-instant (server push).

2. Node.js server — webhook (recommended for server-side backends)

Register a webhookUrl at activation. The Orchestrator POSTs the VerificationResult directly to your server when the session reaches a terminal state. No long-lived connection required.

await verifier.activate({
  licenseKey: 'CS-your-license-key',
  profileId: 'your-profile-uuid',
  webhookUrl: 'https://your-server.com/webhooks/verification',  // HTTPS required
})

const session = await verifier.createVerificationSession({ purpose: 'age_check' })
// The Orchestrator will POST to your webhookUrl when the session completes.
// Your server does not need to call subscribeToVerificationResult().

Verify the X-Credence-Signature header on every incoming webhook (see the webhook signature verification section above).

Latency: near-instant (server push). Preferred over polling for server-side backends.

3. Node.js server — polling fallback (last resort)

If your server-side code cannot accept inbound webhook POSTs (e.g. local development behind NAT, or a serverless function with no public URL), subscribeToVerificationResult() will automatically fall back to polling when running in Node.js (typeof window === 'undefined').

// No webhookUrl registered — SDK falls back to polling automatically
await verifier.activate({ licenseKey: 'CS-your-license-key', profileId: 'your-profile-uuid' })
const session = await verifier.createVerificationSession({ purpose: 'age_check' })

const unsubscribe = await verifier.subscribeToVerificationResult(session.sessionId, result => {
  console.log(result.status)   // 'verified' | 'denied' | 'failed' | 'expired' | 'cancelled'
  console.log(result.decision) // 'verified' when status is 'verified'
  unsubscribe()
})

The interface is identical to the SSE path — the same unsubscribe function is returned. The SDK polls GET /v1/orchestrator/sessions/{id} every 2 seconds until a terminal state is received or unsubscribe() is called.

Latency: up to 2 s (one polling interval). Use webhook instead whenever your server is publicly reachable.


Poll session status

Use getVerificationStatus() to manually check the current state of a session:

const status = await verifier.getVerificationStatus(session.sessionId)
console.log(status.state)   // 'created' | 'qr_displayed' | 'opened' | ... | 'verified'
console.log(status.result)  // VerificationResult — populated when state is terminal

API reference

new CredenceVerifier(config)

| Parameter | Type | Description | |---|---|---| | config.baseUrl | string | Base URL of your Credence ID Online Verifier instance |


activate(config): Promise<void>

Authenticates the SDK with the Credence ID server. Must be called before any other method.

| Parameter | Type | Required | Description | |---|---|---|---| | licenseKey | string | Yes | License key issued by CredenceID (starts with CS-) | | profileId | string | Yes | UUID of the reader profile | | webhookUrl | string | No | HTTPS URL to receive webhook POSTs on session completion |

Throws ActivationError (with .code) on invalid credentials.


createVerificationSession(opts): Promise<VerificationSession>

Creates a new verification session and returns the QR code.

| Parameter | Type | Required | Description | |---|---|---|---| | purpose | string | Yes | Business intent (e.g. 'age_check', 'identity_check') | | referenceId | string | No | Your internal transaction reference for audit | | returnUrl | string | No | Where to send the user back to after the same-device flow completes (see below) |

returnUrl is the recommended way to handle the same-device flow — pass the merchant page's own URL (e.g. window.location.href) and the consumer page will redirect back to it on any terminal state, with cid_session=<sessionId> and cid_status=<verified|denied|failed|expired|cancelled> appended as query params. This works identically across every browser, including iOS Safari, where the alternative openVerificationTab() approach (window.open() + window.close()) is unreliable because Safari often refuses to close script-opened tabs.

Must be https:// and its origin must match the license's registered domain, or session creation throws SessionError with code INVALID_RETURN_URL (not HTTPS / malformed) or RETURN_URL_DOMAIN_MISMATCH (origin not registered to this license).

The query params are a convenience for immediate UI feedback — the authoritative result is still delivered server-side over SSE/webhook, and no PII or credential data is ever included in the URL.

const session = await verifier.createVerificationSession({
  purpose: 'age_check',
  returnUrl: window.location.href,
})
window.location.href = session.qrUrl // same-tab redirect into the consumer page

// On return, read the outcome directly from the URL for immediate feedback
// (subscribeToVerificationResult()/webhook remains the source of truth):
const params = new URLSearchParams(window.location.search)
const returnedStatus = params.get('cid_status') // 'verified' | 'denied' | ... | null

Returns:

{
  sessionId: string       // UUID v4
  qrUrl: string           // URL encoded in the QR code
  qrImageBase64: string   // Base64 PNG data URI — use as <img src="...">
  expiresAt: Date         // When the session expires
}

Throws SessionError (with .code) on API errors. Throws Error if activate() has not been called.


renderQRCode(qrImageBase64, container): { remove: () => void }

Injects a QR code <img> into a DOM container. Browser-only (no-op in Node.js).

| Parameter | Type | Description | |---|---|---| | qrImageBase64 | string | Base64 data URI from createVerificationSession() | | container | string \| HTMLElement | CSS selector string or HTMLElement reference |

Returns { remove() } — call remove() to cleanly delete the injected image.


openVerificationTab(session): Window | null

Opens the session's consumer page (session.qrUrl) in a new tab via window.open() — an alternative same-device counterpart to renderQRCode(). Browser-only (no-op, returns null, in Node.js).

Prefer passing returnUrl to createVerificationSession() instead for the same-device flow — it works reliably on every browser. This method relies on window.opener + window.close(), which iOS Safari frequently refuses for script-opened tabs even when window.opener is correctly set, leaving the user stranded on a "you may close this page" screen with no automatic way back.

| Parameter | Type | Description | |---|---|---| | session | VerificationSession | Session from createVerificationSession() |

Returns the opened Window, or null if the popup was blocked or the call happened outside a browser environment.

Must be called synchronously from within a user-initiated event handler (e.g. a click listener), not after an await or inside a setTimeout/Promise callback — otherwise browsers block it as an unsolicited popup. Calling it this way (instead of a target="_blank" link, which modern browsers treat as noopener) keeps window.opener set on the opened tab, which the consumer page uses to notify the opener and self-close on completion.

verifyButton.addEventListener('click', () => {
  verifier.openVerificationTab(session)
})

subscribeToVerificationResult(sessionId, callback): Promise<() => void>

Listens for the session result and fires callback when the session reaches a terminal state.

| Parameter | Type | Description | |---|---|---| | sessionId | string | Session ID from createVerificationSession() | | callback | (result: VerificationResult) => void | Called once when the result arrives |

Returns a Promise that resolves to an unsubscribe function — await the call before invoking the unsubscribe function.

Transport is selected automatically:

| Environment | Transport | Notes | |---|---|---| | Browser (window defined) | SSE (EventSource) | Low-latency push; preferred for POS web UIs | | Node.js (window undefined) | Polling every 2 s | Fallback; use webhook instead when possible |

See Server-side integration patterns for guidance on which mode to use.


cancelVerificationSession(sessionId): Promise<void>

Cancels an active session. Throws SessionError if the session is not found or already in a terminal state.


getVerificationStatus(sessionId): Promise<SessionStatusResponse>

Fetches the current state of a session.

Types

type SessionStatus =
  | 'created' | 'qr_displayed' | 'opened' | 'in_progress' | 'fallback_started'
  | 'verified' | 'denied' | 'failed' | 'expired' | 'cancelled'

interface VerificationResult {
  sessionId: string
  status: SessionStatus
  purpose: string
  decision?: string    // 'verified' when status is 'verified'
  verifiedAt?: number  // Unix timestamp (ms)
}

interface SessionStatusResponse {
  sessionId: string
  state: SessionStatus
  purpose: string
  referenceId?: string
  qrUrl: string
  qrImageBase64: string
  expiresAt: Date
  result?: VerificationResult
}

class ActivationError extends Error {
  code: string  // e.g. 'INVALID_LICENSE_KEY', 'EXPIRED_LICENSE'
}

class SessionError extends Error {
  code: string  // e.g. 'SESSION_NOT_FOUND', 'UNSUPPORTED_PURPOSE', 'SESSION_ALREADY_TERMINAL',
                //      'INVALID_RETURN_URL', 'RETURN_URL_DOMAIN_MISMATCH'
}

Error handling

import { ActivationError, SessionError } from '@credenceid/verifier-sdk'

try {
  await verifier.activate({ licenseKey: 'bad', profileId: 'x' })
} catch (e) {
  if (e instanceof ActivationError) {
    console.error(e.code, e.message)  // 'INVALID_LICENSE_KEY', 'License key not found'
  }
}

try {
  await verifier.cancelVerificationSession('nonexistent-id')
} catch (e) {
  if (e instanceof SessionError) {
    console.error(e.code)  // 'SESSION_NOT_FOUND'
  }
}

Token lifecycle

The SDK manages access tokens automatically — callers never need to handle token expiry.

| Behaviour | How it works | |---|---| | Proactive pre-expiry refresh | Before each API call the SDK checks whether the token expires within the next 30 seconds. If so, it re-calls activate() with the stored credentials before making the request. | | Transparent 401 recovery | If an API call unexpectedly returns 401 (e.g. the server restarted and invalidated tokens), the SDK re-activates once and retries the request. The caller sees a successful response. | | Concurrent refresh deduplication | If multiple API calls are in-flight simultaneously when the token needs refreshing, only one activate() HTTP request is sent. All concurrent callers wait on the same promise and receive the new token together. |

If re-activation itself fails (e.g. the license key has been revoked), an ActivationError is thrown and propagates to the caller.

import { ActivationError } from '@credenceid/verifier-sdk'

try {
  await verifier.createVerificationSession({ purpose: 'age_check' })
} catch (e) {
  if (e instanceof ActivationError) {
    // Token could not be refreshed — credentials may be invalid or revoked
    console.error('Re-activation failed:', e.code, e.message)
  }
}

Local development

# Build
npm run build

# Watch mode
npm run dev

# Tests
npm test

To test against a local Online Verifier instance:

const verifier = new CredenceVerifier({ baseUrl: 'http://localhost:8080' })