Skip to main content
Use this complete /check route to run the widget and save one receipt after server verification. It uses Node 24+ and persistent SQLite storage. The server client can also be used with your application’s existing database.

Install the example

Download the server examples and put the extracted server-examples directory beside your app’s app directory. Add app/check/route.js:
This route handler serves HTML on GET and verifies the form on POST. Keep /check free of a page.js; the handler owns that route. If your app uses src/app, put server-examples beside src/app instead. Copy the archive’s .env.example values into your app’s .env.local. Run your app’s usual development command and open http://127.0.0.1:3000/check. Keep the secret in server-only environment variables; do not use a NEXT_PUBLIC_ prefix. The initial values use test keys and save a local receipt without real measurements. Follow the production key setup to run real checks on your verified domain.

Bind verification to the action

The shared handler sets an HttpOnly SameSite cookie and saves an attempt before rendering data-cdata. It verifies the token against that attempt’s hostname, site, action and ID. Server policy is uncontradicted; change it together with the widget presentation if your action requires a different policy. Verification failures return 403. Missing proof returns 400. An unavailable API returns 503 and leaves the action pending. Retries use the same token and idempotency key; a completed attempt returns the existing receipt. Replace the receipt write in examples/lib/form-flow.mjs with your business write in the same transaction. Bind your authenticated user and the operation’s data to the pending record. The example’s random cookie identifies a browser session, not an account.

Deployment

Keep the Node runtime. SQLite needs a persistent writable disk; a temporary serverless filesystem cannot preserve the single-use guarantee. On serverless or multiple hosts, keep the client and move the attempt/write transaction into your shared database. The handler sends Cache-Control: no-store and a CSP that allows Heretic scripts and frames. Merge the CSP sources into your app’s policy if you move the form into an existing page. The widget script API supports explicit rendering for client-side navigation.