Install
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.
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:{ 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 providedtx 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.