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

consent-nexus-int

v0.3.3

Published

Backend + browser SDK to collect and verify consent for PII form flows, backed by Consent Nexus CMP.

Readme

consent-nexus-int

Official TypeScript SDK for integrating your applications with Consent Nexus (Consent Management Platform). Use it on the server to authenticate securely, fetch consent notices and purposes, verify whether a user has valid consent, record consent artefacts, and handle CMP webhooks. Browser and React entry points are provided as scaffolds for upcoming in-app consent UI.

Package folder: pii-consent-sdk
npm name: consent-nexus-int
License: MIT


Table of contents


Who this is for

  • Backend engineers wiring forms, APIs, or batch jobs that must not process PII until consent is valid for a given purpose.
  • Integration owners connecting a data fiduciary’s systems to Consent Nexus using API keys and per-fiduciary encryption keys from the CMP dashboard.
  • Teams that need typed TypeScript clients, optional Express routers, and webhook signature verification without hand-rolling auth and signing.

The SDK talks to your CMP base URL (the Consent Nexus deployment URL you were given). Route shapes and OpenAPI details live in your CMP integration documentation—not in this package.


What you can build

| Capability | SDK support | |------------|-------------| | Health / connectivity check before auth | checkCmpReachable, cmp.ping() | | Encrypted token exchange + auto refresh | createCmpClient (default) | | List notices and purpose categories | getConsentNotices, getPurposeCategories | | Resolve email/phone → principal | resolvePrincipal | | Verify consent for one or many purposes | verifyConsent, verifyConsentBatch | | Record standard or HMAC-signed consent | recordConsent, recordSignedConsent | | List / fetch consent records for audit | listConsents, getConsent | | Send consent requests to users | requestConsent | | Verify inbound CMP webhooks | verifyWebhook, Express createCmpWebhookRouter | | Legacy API-key-only verify flow | createVerifyConsentClient |


Requirements

  • Node.js 18+ (uses native fetch and Web Crypto–compatible APIs via node:crypto)
  • TypeScript 5.x recommended for types; JavaScript works with JSDoc or plain imports
  • A Consent Nexus tenant with:
    • Data fiduciary ID
    • Integration API key and secret
    • Integration encryption key (64 hex characters, from CMP → Integrations)
    • CMP base URL (your deployment root, no trailing slash required—the SDK normalizes it)

Optional: Express 4+ if you use the bundled routers (peerDependency, optional).


Installation

npm install consent-nexus-int
# or
pnpm add consent-nexus-int
# or
yarn add consent-nexus-int

Package entry points

The package is published with multiple subpath exports so you only bundle what you need:

| Import path | Use when | |-------------|----------| | consent-nexus-int | Default: server API + shared types (same as ./server today) | | consent-nexus-int/server | Node/backend: client, auth, webhooks, Express helpers | | consent-nexus-int/browser | Future: framework-agnostic browser modal (scaffold) | | consent-nexus-int/react | Future: React wrappers (scaffold) |

Server builds ship as ESM and CommonJS with declaration files. Browser is ESM-only (no require build).


Configuration

Create a client with CmpClientConfig:

| Field | Description | |-------|-------------| | cmpBaseUrl | Root URL of your Consent Nexus CMP instance | | dataFiduciaryId | UUID of your data fiduciary in CMP | | apiKey | Integration API key | | apiSecret | Integration API secret (also used for webhook HMAC and signed consent) | | integrationEncryptionKey | 64-character hex key (32 bytes) for cmp_encrypted_v1 auth envelopes | | tokenRefreshSkewMs | Optional. Refresh access token this many ms before expiry (default: 60_000) |

Store secrets in environment variables or a secrets manager—never commit them to source control.

# Example environment variable names (choose your own convention)
CMP_BASE_URL=https://your-cmp.example.com
CMP_FIDUCIARY_ID=00000000-0000-0000-0000-000000000000
CMP_API_KEY=...
CMP_API_SECRET=...
CMP_INTEGRATION_ENCRYPTION_KEY=...   # 64 hex chars from CMP Integrations

Quick start

import { createCmpClient } from 'consent-nexus-int/server';

const cmp = createCmpClient({
  cmpBaseUrl: process.env.CMP_BASE_URL!,
  dataFiduciaryId: process.env.CMP_FIDUCIARY_ID!,
  apiKey: process.env.CMP_API_KEY!,
  apiSecret: process.env.CMP_API_SECRET!,
  integrationEncryptionKey: process.env.CMP_INTEGRATION_ENCRYPTION_KEY!,
});

// Encrypted auth and token refresh run automatically on the first API call
const notices = await cmp.getConsentNotices({ status: 'active' });

const verification = await cmp.verifyConsent({
  user: { kind: 'email', email: '[email protected]' },
  purpose_id: 'purpose-uuid-here',
});

if (!verification.valid) {
  // Block PII processing; show notice / collect consent
}

await cmp.recordSignedConsent({
  notice_id: notices[0].id,
  notice_record_hash: notices[0].record_hash!,
  purpose_ids: ['purpose-uuid-here'],
  consent_status: 'granted',
  principal: { email: '[email protected]' },
});

CMP client API

createCmpClient(config) returns a client object. All methods use Bearer tokens from the internal token manager unless noted.

Authentication

| Method | Description | |--------|-------------| | authenticate(requestedScopes?) | Explicit encrypted token exchange. Scopes: 'read' \| 'write'. | | getAuthHeaders() | Returns { Authorization: 'Bearer …' } for custom fetch calls. | | fetchJson<T>(path, init?) | Authenticated JSON request; retries once on 401 after refreshing token. |

Catalogue and principals

| Method | Description | |--------|-------------| | getConsentNotices({ status? }) | Returns active, draft, or archived notices (filter via status). | | getPurposeCategories() | Purpose catalogue for your fiduciary. | | resolvePrincipal({ email?, phone? }) | Returns { exists, principal_id, status }. |

Verification

| Method | Description | |--------|-------------| | verifyConsent({ user, purpose_id }) | Whether the user has valid consent for one purpose. | | verifyConsentBatch({ user, purpose_ids }) | Parallel checks; array of { purpose_id, result }. |

Recording and sync

| Method | Description | |--------|-------------| | recordConsent(input) | Standard consent artefact (see Recording consent). | | recordSignedConsent(input) | High-assurance record with canonical payload + HMAC (see Signed consent artefacts). | | listConsents(filters?) | Paginated list; filters: status, data_principal_id, notice_id, updated_since, page, limit. | | getConsent(consentId) | Single consent detail for audit. | | requestConsent({ notice_id, email?, phone?, recipients? }) | Trigger a consent request to one or more recipients. |

Connectivity

| Method | Description | |--------|-------------| | ping(options?) | Same as checkCmpReachable for this client’s cmpBaseUrl. |

Webhooks

| Method | Description | |--------|-------------| | verifyWebhook(rawBody, signatureHeader) | Validates X-Webhook-Signature (HMAC-SHA256 hex). | | acknowledgeWebhookDelivery(deliveryId) | Best-effort delivery ack when X-Webhook-Delivery-Id is present. |

Properties

| Property | Description | |----------|-------------| | config | Read-only copy of CmpClientConfig used to create the client. |


Consent verification

Identifying users (ConsentUser)

Verification accepts a discriminated user object:

type ConsentUser =
  | { kind: 'principal_id'; principal_id: string }
  | { kind: 'email'; email: string }
  | { kind: 'phone'; phone: string }
  | { kind: 'hash'; hash: string };

Email is normalized to lowercase for cache keys and resolution. Phone and hash are trimmed.

Result shape (VerifyConsentResult)

{
  valid: boolean;
  consent?: {
    id?: string;
    notice_id?: string;
    consent_status?: string;
    expires_at?: string | null;
    purpose_ids?: string[];
    updated_at?: string;
  };
  principal_id?: string | null;
  cache?: 'hit' | 'miss';  // when using createVerifyConsentClient cache
}

Recommended path (full CMP client)

Use cmp.verifyConsent so auth, principal resolution, and integration verify routes stay consistent:

const result = await cmp.verifyConsent({
  user: { kind: 'email', email: '[email protected]' },
  purpose_id: 'purpose-uuid',
});

Legacy verify-only client

For existing integrations that supply their own auth headers (e.g. raw API key headers):

import { createVerifyConsentClient } from 'consent-nexus-int/server';

const verify = createVerifyConsentClient({
  cmpBaseUrl: process.env.CMP_BASE_URL!,
  dataFiduciaryId: process.env.CMP_FIDUCIARY_ID!,
  getAuthHeaders: async () => ({
    'x-api-key': process.env.CMP_API_KEY!,
    'x-api-secret': process.env.CMP_API_SECRET!,
  }),
  cache: {
    enabled: true,
    maxEntries: 10_000,
    ttlMs: 60_000,
    negativeTtlMs: 15_000,
  },
});

const result = await verify.verifyConsent({
  user: { kind: 'email', email: '[email protected]' },
  purpose_id: 'purpose-uuid',
});

Optional getPrincipalId lets you resolve principals from your own directory without calling CMP resolve.

When cmpClient is passed inside verify config (used internally by createCmpClient), verification uses the integration auth path and Bearer tokens.


Recording consent

recordConsent

Posts a consent artefact with method default consent_nexus_int_v1. Important fields:

| Field | Required | Notes | |-------|----------|-------| | notice_id | Yes | Notice UUID | | purpose_ids | Yes | One or more purpose UUIDs | | consent_status | Yes | 'granted' or 'denied' | | principal | Often | { email?, phone? } when no data_principal_id | | data_principal_id | Optional | If already known | | session_id, language_preference, expires_at | Optional | Audit / UX metadata | | ip_address, user_agent, device_info | Optional | Collection context | | collection_metadata, ui_proof, org_map_id | Optional | Evidence and mapping |

recordSignedConsent

Extends recordConsent with:

| Field | Required | Notes | |-------|----------|-------| | notice_record_hash | Yes | Hash from the notice row at time of collection (tamper-evident linkage) |

The SDK builds a canonical JSON payload, signs it with your API secret (HMAC-SHA256), and sends the signature in a dedicated request header. Use this for high-assurance flows where notice version integrity matters.

Always use the current record_hash from getConsentNotices when the user accepts a notice.


Webhooks

CMP can POST events (e.g. consent created, withdrawn) to your backend.

Signature verification

  • Algorithm: HMAC-SHA256 over the raw request body, hex-encoded digest.
  • Header: X-Webhook-Signature
  • Secret: your integration apiSecret
const ok = cmp.verifyWebhook(rawBody, req.headers['x-webhook-signature']);

Use the raw body string or buffer—do not re-serialize parsed JSON before verifying.

Delivery acknowledgement

If CMP sends X-Webhook-Delivery-Id, call acknowledgeWebhookDelivery(deliveryId) after you process the event (the Express helper does this when a full cmpClient config is provided).

Event handling

Event payloads are JSON objects; shape depends on event type. Handle them in your onEvent callback and keep processing idempotent.


Express integration

Express is an optional peer dependency. Pass your express module so the SDK does not pin a duplicate copy.

Verify proxy router

Mount createConsentSdkExpressRouter to expose a small JSON API for your frontend or BFF:

  • POST /verify — body: { purpose_id: uuid, user: ConsentUser }{ success: true, data: VerifyConsentResult }
import express from 'express';
import { createConsentSdkExpressRouter, createCmpClient } from 'consent-nexus-int/server';

const app = express();
app.use(express.json());

const cmp = createCmpClient({ /* ... */ });

app.use(
  '/consent-sdk',
  createConsentSdkExpressRouter({
    express,
    verify: {
      cmpBaseUrl: process.env.CMP_BASE_URL!,
      dataFiduciaryId: process.env.CMP_FIDUCIARY_ID!,
      cmpClient: cmp, // preferred: uses encrypted auth
    },
  }),
);

Webhook router

Mount createCmpWebhookRouter with express.raw({ type: 'application/json' }) on the same path so rawBody is available for signature verification:

app.use(
  '/webhooks/cmp',
  express.raw({ type: 'application/json' }),
  createCmpWebhookRouter({
    express,
    apiSecret: process.env.CMP_API_SECRET!,
    cmpClient: { /* full CmpClientConfig for auto-ack */ },
    onEvent: async (event) => {
      // Handle consent_withdrawn, consent_created, etc.
    },
  }),
);

Encrypted authentication

By default, createCmpClient does not send API keys on every request. Instead it:

  1. Builds a cmp_encrypted_v1 envelope: AES-256-GCM over JSON { api_key, api_secret, nonce, issued_at, requested_scopes? }.
  2. Exchanges the envelope for access and refresh tokens via CMP’s integration auth API.
  3. Attaches Authorization: Bearer <accessToken> to subsequent calls.
  4. Refreshes before expiry (with configurable skew) and retries once on HTTP 401.

The integration encryption key must be exactly 64 hexadecimal characters (32 bytes). Obtain it from CMP → Integrations for your fiduciary.

Advanced use: import encryptAuthEnvelope, decryptAuthEnvelope, and CMP_ENCRYPTED_FORMAT_V1 for testing or custom auth flows. Full wire format is specified in your CMP integration documentation.

createTokenManager(config) exposes the same lifecycle for custom clients.


Signed consent artefacts

Exported helpers for custom pipelines:

| Export | Role | |--------|------| | buildSignedArtefactPayload(...) | Stable field set for signing | | canonicalizeJson(value) | Deterministic JSON serialization (sorted keys) | | signCanonicalPayload(canonical, apiSecret) | HMAC-SHA256 hex signature |

recordSignedConsent uses these internally. Payload includes fiduciary id, notice id, notice record hash, purpose ids, consent status, principal identifiers, issued_at, and a random nonce.


Reachability checks

Verify CMP is up before exchanging credentials (no API key required):

import { checkCmpReachable, createCmpClient } from 'consent-nexus-int/server';

const ping = await checkCmpReachable(process.env.CMP_BASE_URL!, {
  timeoutMs: 10_000,
  deep: true, // optional: deeper dependency check when supported by your CMP version
});

if (!ping.reachable) {
  throw new Error(ping.error ?? 'CMP unreachable');
}

console.log(ping.status, ping.latencyMs, ping.version);

Or via the client:

const status = await cmp.ping({ deep: false });

CmpPingResult includes reachable, status ('ok' | 'degraded' | 'error'), latencyMs, optional version / timestamp, and error on failure.


Low-level utilities

| Export | Module | Purpose | |--------|--------|---------| | createCmpClient | server | Main integration client | | createVerifyConsentClient | server | Verify-only with optional LRU cache | | checkCmpReachable | server | Unauthenticated health check | | createTokenManager | server | Token lifecycle without full client | | encryptAuthEnvelope / decryptAuthEnvelope | server | cmp_encrypted_v1 crypto | | verifyWebhookSignature | server | Standalone webhook HMAC check | | acknowledgeWebhookDelivery | server | Standalone delivery ack | | createConsentSdkExpressRouter | server | Express verify BFF | | createCmpWebhookRouter | server | Express webhook handler | | Shared types | main / server | CmpClientConfig, ConsentNotice, etc. |


Browser and React (preview)

Entry points consent-nexus-int/browser and consent-nexus-int/react are scaffolds for a future embedded consent modal (initialize, record consent, framework-agnostic events). They currently export minimal types and placeholders—do not rely on them in production until released in changelog notes.

Planned direction:

  • initialize({ apiBaseUrl, language? }) — load notice config and render UI
  • recordConsent — submit choices back through CMP
  • React wrappers around the same core

Track package version releases for browser support.


Security practices

  • Run the SDK only on trusted servers—API secret and encryption key must never ship to browsers or mobile apps.
  • Use encrypted auth (createCmpClient) instead of sending raw API secrets on every request when CMP supports it.
  • Rotate API keys and integration encryption keys through CMP when credentials may be exposed.
  • For webhooks, always verify X-Webhook-Signature on the raw body; reject unsigned or mismatched requests with HTTP 401.
  • Treat verifyConsent as a gate before PII processing; cache negative results briefly but respect withdrawal webhooks to invalidate local state.
  • Log errors without logging secrets, full tokens, or decrypted envelopes.

Error handling

  • Failed HTTP responses throw Error with CMP’s error message when present, or a generic Request failed (status) message.
  • Auth failures surface as Auth failed (status) or Auth response missing tokens.
  • Invalid integrationEncryptionKey throws at encrypt time: must be 64 hex characters.
  • Webhook ack failures throw with CMP error text when available.
  • Express routers return 400 for validation errors, 401 for bad webhook signatures, 500 for unexpected verify failures.

Wrap client calls in your application’s retry/backoff policy for transient network errors; the client already retries once on 401 after token refresh.


Development and publishing

From this package directory:

pnpm install
pnpm run typecheck
pnpm run build
pnpm test

| Script | Description | |--------|-------------| | pnpm run build | tsupdist/ (ESM + CJS + .d.ts) | | pnpm run dev | Watch mode rebuild | | pnpm run test | Vitest unit tests | | pnpm run test:live | Optional live tests against a running CMP (set CMP_BASE_URL in env) | | pnpm run lint | ESLint on src/ |

prepublishOnly runs typecheck, build, and tests before npm publish.

Live integration tests

Point tests at your CMP instance (must be running and configured with valid fiduciary credentials in your test environment):

CMP_BASE_URL=https://your-cmp.example.com pnpm run test:live

Do not commit real secrets; use CI secrets or local .env files ignored by git.


Typings

TypeScript definitions ship in dist/*.d.ts. Import types from the same paths as runtime:

import type { CmpClientConfig, VerifyConsentResult, ConsentNotice } from 'consent-nexus-int/server';

Versioning and support

  • Follow semantic versioning on the npm package.
  • Pin a major version in production and read release notes before upgrading.
  • For CMP API changes (new fields, auth formats), upgrade SDK and CMP together per your vendor’s compatibility matrix.

Related documentation

  • Consent Nexus CMP — integration keys, notices, purposes, and fiduciary setup (your deployment’s admin docs).
  • Integration API reference — authoritative request/response schemas and route list (provided with your CMP license; not duplicated here to avoid drift and exposure of deployment-specific internals).

License

MIT © ProgIST Solutions. See LICENSE.