Skip to main content
The application SDK handles preparation, waiting, challenges and continuation. Your backend supplies the authenticated account, trusted request address, eligibility rule and transactional write. Use Node 22+ and PostgreSQL 14+. The complete Next.js example includes all files and local fixtures.

Install

Set HERETIC_SITE_KEY, HERETIC_SECRET_KEY, APP_ORIGIN and DATABASE_URL in the server environment or a local .env file. Verify the site’s domain in the dashboard.
The migration adds integration tables to your database. The check command validates configuration, server credential access, public verification keys and database connectivity without creating a measurement or ceremony. The package README ships with the API definitions and retention operations.

Connect the server

createIntegration from @heretic-hq/integration/server accepts: Mount handle(request) behind /api/heretic/*, forwarding a standard Request and returning its Response. The returned GET and POST handlers can also be mounted directly in a Next.js catch-all route with runtime = 'nodejs'. The IP resolver must match your deployment. Do not read an arbitrary forwarding header unless your ingress overwrites it and direct origin access is restricted appropriately. The adapter does not assume a particular CDN. It binds the signed measurement to your application session by default, using a nonce captured when measurement starts. To require an additional address or request relation, supply requestBinding({device, context, phase}); it must return true. The application and sensor may observe different IP families. Signature, freshness, session binding and account checks always apply. Every public route that can perform the protected write must use the same server guard. When replacing an existing endpoint, remove its unguarded write path or route it through the adapter. Changing only the frontend submission leaves an older API callable directly.

Define the action

Each action has these functions: The policy receives validated evidence, the server-read challenge when one ran, accountUsed, and cross-account matches for completed actions with that resource key. It also receives the authenticated account, normalized input, attempt ID, receipt time and PostgreSQL transaction client. A valid signature authenticates a record; it does not grant access. Your policy decides what to do with a contradicted, refused or concealed measurement. A passing ceremony does not override an existing eligibility restriction. The reference app implements a complete allocation transaction. It refuses prior redemptions, compares identifier observations, requests a ceremony when its rule requires one, and writes the allocation and receipt together. The registration example creates a server-owned pending registration first and uses its ID with the server session as the action context. It creates the account record only in commit. This supplies a bound identifier before login without accepting a browser-submitted account ID. Existing single-use signup/form integrations remain available. The checkout example uses the same contract for server-calculated prices, stock allocation and an order with a payment outbox task.

Submit from the browser

After initializing the client:
These application display functions handle your result presentation. The SDK supplies waiting and ceremony UI. It never replaces the server’s decision with a browser callback. The SDK keeps pending attempt IDs in sessionStorage for the same authentication context, action and input. If browser storage is unavailable or your application manages retries, pass { attemptId: previousAttemptId } explicitly. Reusing an ID with changed inputs is refused. client.status() refreshes the authentication context after login/account changes.

Complete once

The adapter saves pending attempts and completion receipts in PostgreSQL. It serializes the shared resource before checking policy and committing. Multiple application instances can handle retries; a completed attempt returns its saved receipt. Use the provided tx for every database write that must be atomic with completion. Keep existing unique constraints and business checks. Do not perform an external payment, email or other irreversible API call directly in commit; write an outbox entry and use the external service’s idempotency mechanism. No transaction stays open while the visitor completes a measurement or phone ceremony. A timeout does not prove that an action failed to commit. Retry the same attempt and read its receipt.

Match observations

matches identifies other accounts whose completed actions share observed values, with separate reasons such as probe.ip, probe.instance and probe.machine. Equal identifiers are conditional evidence, not proof of a unique person. Identifier comparisons require the same namespace and version. Probe, ceremony-phone and desktop-handoff records remain distinct. Store policy reasons and results for review. Completed claim history is useful beyond the lifetime of the measurement cookie. Choose retention according to your eligibility rules, retry window and deletion obligations; removing a receipt/history entry removes that entry from future checks.

Limited resources

The example awards in verified transaction order. Receipt of an earlier request does not reserve inventory. An arrival-order policy needs an explicit qualification deadline, rules for earlier unfinished attempts and protections against reservation abuse. Those allocation choices belong in your application policy.

Failure states

client.execute(action, input, { attemptId, restart: true }) atomically retires an unfinished attempt before creating a replacement. If the original already completed, the server returns its receipt. Expose this as an explicit user action; ordinary retries retain the existing attempt. Use onEvent on the server for readiness, elapsed time, outcome and diagnostic reasons. The callback omits tokens and raw account/address values. An onState callback supplies browser feedback.