@credenceid/verifier-sdk
v0.1.10
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-sdkQuick 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
const qr = verifier.renderQRCode(session.qrImageBase64, '#qr-container')
// 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 terminalAPI 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 |
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.
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'
}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 testTo test against a local Online Verifier instance:
const verifier = new CredenceVerifier({ baseUrl: 'http://localhost:8080' })