Consent Docs

Node.js helpers

The helper functions of @piedap/consent/server for webhook signatures, identity proofs, fingerprints, capture hashes, skipped imports, cookies and receipts.

Reference5 min readFor developers using the browser runtime and the server and mobile SDKs

@piedap/consent/server exports helper functions next to the client. They check webhook signatures, compute proofs, fingerprints and capture hashes, spot skipped imports, read the consent cookie and verify receipts.

Requirements#

  • Node.js 18 or later.
  • For verifyWebhook(): the endpoint's signing secret, whsec_….
  • For identityProof(): the key's identity secret, idv_….
  • For subjectFingerprint() and captureReferenceHash(): the project's fingerprint key, sfk_….

Install#

The helpers ship in the same package as the client. See Install.

Import the helpers#

JavaScript
import {
  verifyWebhook,
  identityProof,
  subjectFingerprint,
  captureReferenceHash,
  isSkippedImport,
  parseConsentCookie,
  consentFromCookieHeader,
  verifyReceipt,
  canonicalize,
} from '@piedap/consent/server';

The helpers are plain functions. They make no network calls and need no client.

Functions#

verifyWebhook()#

Checks that a webhook delivery is genuine and recent. Pass one object with these fields:

Parameter Type Required or default Description
payload string | Buffer Required The raw request body, not parsed JSON.
signature string | null | undefined Required The x-piedap-signature header.
timestamp string | null | undefined Required The x-piedap-timestamp header.
secret string Required The endpoint's signing secret.
toleranceSeconds number 300 How far the timestamp may be from now, in either direction.
now number Date.now() The current time in milliseconds.

Returns boolean: true only when the signature matches the raw body and the timestamp is within the tolerance.

Errors A payload that is not a string or a Buffer, such as parsed JSON, can throw a TypeError. A missing header, an empty secret or a malformed value gives false.

JavaScript
import {
  SIGNATURE_HEADER,
  TIMESTAMP_HEADER,
  verifyWebhook,
} from '@piedap/consent/server';

const ok = verifyWebhook({
  payload: req.body, // the raw bytes
  signature: req.get(SIGNATURE_HEADER),
  timestamp: req.get(TIMESTAMP_HEADER),
  secret: process.env.PIEDAP_WEBHOOK_SECRET,
});

See Verify signatures.

identityProof()#

Computes the proof that lets a browser or an app record consent under a signed-in person.

Parameter Type Required or default Description
identitySecret string Required The key's identity secret.
subjectType string Required EMAIL, USER_ID, PHONE or DEVICE_ID.
subjectId string Required The identifier, exactly as the page or app sends it.

Returns string: the hex HMAC-SHA256 of <subjectType>:<subjectId>.

Errors Throws a TypeError when identitySecret is missing, such as an unset environment variable. The other arguments are not checked, so keep the order: type first, then the identifier.

JavaScript
const proof = identityProof(
  process.env.PIEDAP_IDENTITY_SECRET,
  'USER_ID',
  user.id,
);

See Signed-in visitors.

subjectFingerprint()#

Computes the fingerprint that receipts and webhooks carry for a person.

Parameter Type Required or default Description
fingerprintKey string Required The project's fingerprint key.
subjectId string Required The identifier you recorded consent under.
subjectType SubjectType None The kind of identifier. Without it, a value that contains @ is treated as an email address.

Returns string: the hex HMAC-SHA256 of the identifier. An email address is put in lower case first.

Errors Throws a TypeError when fingerprintKey is missing. Here the identifier comes before the type.

JavaScript
const fingerprint = subjectFingerprint(
  process.env.PIEDAP_FINGERPRINT_KEY,
  'usr_8f2K1xQz',
  'USER_ID',
);

See Match events to users.

captureReferenceHash()#

Computes the agentRefHash or locationRefHash that a capture context carries, so you can match it to your own staff id or site code.

Parameter Type Required or default Description
fingerprintKey string Required The project's fingerprint key.
kind 'agent' | 'location' Required agent for a member of staff, location for a branch or site.
reference string Required Your id, as sent in capture.agentRef or capture.locationRef.

Returns string: the hex HMAC-SHA256 of <kind>:<reference>, with the reference trimmed and in lower case.

Errors Throws a TypeError when fingerprintKey or reference is missing, or kind is neither value.

JavaScript
const agentRefHash = captureReferenceHash(
  process.env.PIEDAP_FINGERPRINT_KEY,
  'agent',
  'EMP-40213',
);

See Record consent taken in a branch, by phone or on paper.

isSkippedImport()#

Tells a write with collectedAt that the records overrule from a real error.

Parameter Type Required or default Description
error unknown Required What a recordConsent() call threw.

Returns boolean: true for a ConsentApiError with status 409 and one of the codes in SKIPPED_IMPORT_CODES.

Errors None.

JavaScript
try {
  await client.recordConsent(row);
} catch (error) {
  if (!isSkippedImport(error)) throw error;
  skipped.push({ row: row.idempotencyKey, code: error.code });
}

See Bring in consent given earlier.

parseConsentCookie()#

Reads the two cookie values the browser runtime wrote.

Parameter Type Required or default Description
value string | null | undefined Required The piedap_consent cookie value.
partnersValue string | null None The piedap_partners cookie value.

Returns a ConsentCookie, or null.

Errors None. null comes back when the visitor has not answered or the cookie is malformed.

JavaScript
const consent = parseConsentCookie(
  req.cookies.piedap_consent,
  req.cookies.piedap_partners,
);

consentFromCookieHeader()#

Finds both consent cookies in a whole Cookie request header and reads them.

Parameter Type Required or default Description
header string | null | undefined Required The Cookie header.

Returns a ConsentCookie, or null.

Errors None. null comes back when the visitor has not answered or the cookie is malformed.

JavaScript
app.get('/', (req, res) => {
  const consent = consentFromCookieHeader(req.get('cookie'));
  const analytics = consent?.granted('analytics') ?? false;
  const matomo = consent?.granted('analytics', 'matomo') ?? false;
  res.render('home', { analytics, matomo });
});

verifyReceipt()#

Checks an Ed25519 receipt against the deployment's public keys without calling the API.

Parameter Type Required or default Description
signed SignedReceipt Required A receipt from receipt() or exportSubject().
keys { keys: Jwk[] } | Jwk[] Required The keys from publicKeys().

Returns boolean: true when the signature matches.

Errors None. It returns false for anything it cannot vouch for, including older HMAC-SHA256 receipts. Only the issuing deployment can check those, through POST /sdk/v1/receipts/verify.

JavaScript
const keys = await client.publicKeys();
const pack = await client.exportSubject('usr_8f2K1xQz', 'USER_ID');

for (const signed of pack.receipts) {
  console.log(signed.receipt.consentId, verifyReceipt(signed, keys));
}

canonicalize()#

Returns the exact string a receipt signature covers.

Parameter Type Required or default Description
value unknown Required The receipt document.

Returns string: JSON with the keys sorted at every depth and undefined values dropped.

Errors None.

JavaScript
const signedText = canonicalize(signed.receipt);

You need it only to check a signature with your own code.

Types#

ConsentCookie#

Field Type Description
consentId string | null The record's id. null for the short time between the visitor's answer and the API confirming it.
formVersion number The version of the form that was answered.
decisions Record<string, boolean> Purpose key to allowed.
decidedAt Date When the visitor answered.
partners Record<string, string[]> Purpose key to the partner keys the visitor refused.
granted(purposeKey, partnerKey?) boolean Whether the purpose is allowed. With partnerKey, whether that partner is allowed too.

The cookie is not evidence

A visitor controls their own cookies. Use the cookie to decide what to render. Where the answer must be right, read the record with getConsent().

The cookie formats are in Cookies and browser storage. SignedReceipt and Jwk are in Types.

Constants#

Name Value
SIGNATURE_HEADER x-piedap-signature
TIMESTAMP_HEADER x-piedap-timestamp
USER_AGENT piedap-consent-node/1.0.7
CHECK_TIMEOUT_MS 1000, how long checkConsent() waits unless told otherwise
SKIPPED_IMPORT_CODES CONSENT_WITHDRAWN_ON_RECORD, CONSENT_ALREADY_ON_RECORD, CONSENT_NEWER_ON_RECORD, CONSENT_ERASED_ON_RECORD, CONSENT_HELD_UNDER_OTHER_IDENTIFIER

↑↓ to move, Enter to open