# Heedeo package quickstart Pilot package for Node.js 20.18+, Next.js App Router, and Cloudflare Workers with `nodejs_compat`. It handles browser loading, same-domain forwarding, event requests, timeouts and one retry. Heedeo returns signals; your app still owns signup and authentication. ## 1. Install ```sh npm install @heedeo/sdk ``` The package is published on npm as `@heedeo/sdk`. It has no runtime dependencies. Get your keys at https://app.heedeo.com/review/sign-up. Save the secret in your server's environment settings, including your deployed environment. Never put it in a `NEXT_PUBLIC_` variable. ```sh HEEDEO_SECRET_KEY=sk_your_secret_key NEXT_PUBLIC_HEEDEO_KEY=pk_your_publishable_key ``` ## 2. Configure the server once ```ts // lib/heedeo.ts: import only from server files import { Heedeo } from '@heedeo/sdk'; import { trustedClientIp } from './trusted-client-ip'; export const heedeo = new Heedeo({ secretKey: process.env.HEEDEO_SECRET_KEY, clientIp: trustedClientIp, }); ``` `trustedClientIp(request)` is the one hosting-specific piece. It must return the real visitor's IP from a source your server trusts, or `null` when unavailable. Use the same function for the proxy and signup. The package deliberately does not guess whether an incoming forwarded header is trustworthy. For local testing only: ```ts // lib/trusted-client-ip.ts export function trustedClientIp(_request: Request): string | null { if (process.env.NODE_ENV === 'development') return '127.0.0.1'; return null; // Configure your host's trusted IP source before deploying. } ``` For a Cloudflare-proxied app, `cf-connecting-ip` is an option only when your origin rejects requests that bypass that trusted proxy. Do not assume that a public Railway origin is protected just because the custom domain uses Cloudflare. Have your coding assistant inspect the actual deployment and configure this function. A missing IP returns a setup error; it does not silently group all production visitors under localhost. ## 3. Add one route, or use your endpoint Your app forwards Heedeo's two paths itself. If the Domains page (https://app.heedeo.com/review/domains) already serves Heedeo from your domain, skip this step and pass its endpoint instead (step 4). ```ts // app/hd/[...path]/route.ts import { heedeo } from '../../../lib/heedeo'; export const runtime = 'nodejs'; export const GET = heedeo.proxy; export const POST = heedeo.proxy; ``` This assumes `lib` is at the project root, as in the runnable example. Keep `/hd/i.js` and `/hd/v1/identify` reachable before sign-in. Existing authentication middleware must allow those two paths. Only these endpoints are forwarded; this route cannot forward event submissions or arbitrary URLs. Cookies and request headers unrelated to Heedeo are not sent upstream. ## 4. Identify just before signup ```ts // lib/heedeo-browser.ts: safe to import in a client component import { createHeedeo } from '@heedeo/sdk/browser'; export const heedeo = createHeedeo({ publishableKey: process.env.NEXT_PUBLIC_HEEDEO_KEY, }); ``` In your existing submit handler: ```ts const { data } = await heedeo.identify(); await signup({ email, heedeoToken: data?.token }); ``` Keep your existing `signup` function and its validation, loading state and error handling. The package loads the script once, waits for it, and returns a new receipt for each call. No script tag or global type declaration is needed. Identification happens at the action, not on every page load. If identification fails, `data` is `null` and `error` explains the problem; continue signup without signals. Do not mix the package with an existing Heedeo script tag. With an endpoint from the Domains page, pass it as `endpoint` in place of `path` and add no route, for example `createHeedeo({ publishableKey, endpoint: 'https://www.customer.com/k9m2x' })`. `endpoint` comes in 0.1.0-pilot.11, which is not on npm yet; until it is, use the route. It is an HTTPS origin with at most one path segment and no trailing slash; the script loads from `${endpoint}/i.js`. Passing both `path` and `endpoint` throws. ## 5. Record the event on your server In the existing signup route, after validating the submitted fields: ```ts const { data, error } = await heedeo.event({ request, token: input.heedeoToken, action: 'signup', email: input.email, }); if (data) console.info('Heedeo event', data.event_id); if (error) console.warn('Heedeo setup', error.code); // Continue your existing signup. Observe only during the pilot. ``` `request` is the original Web Request received by a Next.js route handler; it preserves the actual URL, method and headers. The call does not read or consume its body. Route handlers are the supported Next.js path for this first package release. Keep an existing server-action integration on the manual guide until an adapter has been tested; do not invent a request URL or replace authentication to use the package. The SDK generates a request ID once and reuses the same body on its one retry. If your application retries the entire action, provide its stable ID as `requestId`. A new user action needs a new identification and ID. Do not send two events with the same receipt to attach a user ID later. ## Check that it works Run a test signup and check the server result: an `event_id` and a non-null `visitor` confirm a usable identification. `flag: false` is normal; an empty flags screen is not evidence of failure. Check that signup still completes with the script blocked or Heedeo unavailable. Never expose the full signals response or internal reasons to an end user. Use HTTPS for deployed testing, including Safari's secure-cookie behavior. Review the privacy wording in https://app.heedeo.com/docs#7-what-heedeo-never-does before going live. ## Options and errors Server options: `secretKey`, required `clientIp(request)`, optional `path` (default `/hd`), `timeoutMs` (default 3000 total across event retries), `baseUrl` (default `https://app.heedeo.com`), and `fetch` for testing. Custom origins require HTTPS, except localhost. Browser options: `publishableKey`, matching `path` or an `endpoint` on your domain, and `timeoutMs` (default 3000 total for script loading and identification). Configuration mistakes throw when you construct the client, so missing keys are caught at setup. Operational failures return `{ data: null, error: { code, message, status? } }`; checking `error` narrows `data` to the success value. `code` is a `HeedeoErrorCode`, so a `switch` over it is checked by TypeScript: - Server: `missing_token`, `missing_client_ip`, `invalid_key`, `api_error`, `invalid_response`, `network_error`, `timeout_or_aborted`, `configuration_error`. - Browser: `browser_required`, `timeout`, `invalid_response`, `identification_unavailable`. Neither entry point logs credentials, request bodies or upstream error bodies. Browser timeouts stop waiting, but cannot cancel an already-running collector call in this pilot version. Always pass the returned token explicitly; never fall back to an older cookie token after a timeout. Browser results are not security decisions. Secret keys and event signals stay on your server. ## Pilot boundaries This package is an ESM package (use `import`) for Node.js servers. The browser entry is separate and imports no server code. The server entry supports Node.js and Cloudflare Workers with `nodejs_compat`. Other edge runtimes have not been verified. No change to your hosting, database or sign-in provider is required. React hooks, server-action adapters, Express-specific wrappers and an automatic installer are not included in this release. ## Cloudflare Workers Enable `nodejs_compat` in Wrangler. Create the client inside the request handler so keys come from Worker secrets: ```ts import { Heedeo } from '@heedeo/sdk'; export default { async fetch(request, env) { const heedeo = new Heedeo({ secretKey: env.HEEDEO_SECRET_KEY, clientIp: request => request.headers.get('cf-connecting-ip'), }); if (new URL(request.url).pathname.startsWith('/hd/')) { return heedeo.proxy(request); } // In your signup handler, use heedeo.event() as above. return env.ASSETS.fetch(request); }, }; ``` This IP resolver is for a Worker receiving requests through Cloudflare, not an unprotected origin. If you use static assets, set `assets.run_worker_first` for `/hd/*` and your signup route. Store `HEEDEO_SECRET_KEY` with `wrangler secret put`; only the publishable key belongs in the browser.