Set up Heedeo in this app the way a forward deployed engineer would: study the code, choose, recommend, build, prove, hand over. Heedeo tells the app's server who is behind a signup and how suspicious it is. It blocks no one: the app's code decides. Read https://app.heedeo.com/docs/sdk.md before you edit, and follow it over anything you remember. https://app.heedeo.com/docs/integration.md is the full guide and it is long: open only the sections named here. Where the two differ from this text, follow this text. If you cannot open them, ask the customer. Do not guess the API. ## 1. Study Read the code first, and change nothing. Find: - The framework, package manager and host, and in a workspace the app that holds the signup. If Heedeo is already here, extend it. - Every path on the app's own server that creates an account: the signup form or route, each OAuth and social sign-in, any API route, mobile backend or invite flow. Each calls Heedeo before the account is created. Then two places: where a new account first reaches it, and the call that grants what an abuser wants (a trial, credits, a coupon). With its own signup handler both are usually that handler. With a sign-in provider called from the browser (Supabase Auth, Clerk, Auth0, Firebase) the server never sees the signup: the first place is the first call after sign-in, and the grant is often a later call. With static pages and an API on another origin, read the guide's "A single-page app, a hosted sign-in and a separate API". - Any email or phone verification step, and how a refused person could reach support. ## 2. Choose Ask the customer this, in exactly these words, and wait for the answer. If you have a tool for asking questions with choices, use it. If they have already told you what they want, say which choice that means instead of asking. How should Heedeo handle signups? You can change this at any time. 1. Let Heedeo decide (Recommended) Heedeo scores every signup and refuses the risky ones. 2. Set my own rules I'll suggest rules for your app. You make the final call. 3. Just watch Record every signup and refuse no one. Then follow the matching path below and ignore the other two. Path: let Heedeo decide. Refuse a signup when Heedeo flags it: ```ts const refuse = data != null && data.listed !== "allow" && data.flag; ``` - Heedeo's rules decide which risks judge each action, from its name, so the action's spelling matters and nothing else is needed on the call. - If the app already has an email or phone verification step, require it when `suggested` is `verify`. Do not add one. - Tell the customer that How strict under Settings (Relaxed, Balanced, Strict) moves this line with no deploy. Path: set my own rules. You suggest, and the customer makes the final call. - Read https://app.heedeo.com/docs/rules.md before you write a rule. It says what to look for in this code, how strong each signal is, the shape of a rule and a rule to start from. - Start from the clear signals, which a rule can refuse on alone, and suggest what this app needs beyond them: ```ts // The signals this app refuses on. Each must fire at full weight (100, read from base, so it holds // whatever weights the workspace sets): a repeat or an abuse report that rests on a weaker link, // such as a similar device or a shared address, passes. const REFUSE_ON = new Set(["repeat_action", "disposable_email", "no_mx", "listed_block", "confirmed_abuse", "receipt_reused"]); const refuse = data != null && data.listed !== "allow" && data.reasons.some((r) => REFUSE_ON.has(r.code) && (r.base ?? r.weight) >= 100); ``` - Put the rules you suggest in your message, in plain words, and write only the ones the customer agrees to. - A rule reads the answer of `evaluate()`: `decision`, `risks`, and each reason's `base` for a clear signal. Path: just watch. Refuse no one and add no verification step. - Call `evaluate()` where a rule would run, and leave a short comment there saying where the rule goes. A person who signs up twice shows as a repeat from the action's name alone. - Add `listed`, and `decision` and `risks` as JSON, to the log line. That log is how the customer will judge a rule before turning one on. ## 3. Recommend Send the customer one short message in plain English and wait for the reply before you edit. Say what you found, what you will do and what a refused person will see. No field names, scores or code. Ask at most two questions, only what the code could not tell you, each with a default. End with exactly: Anything you'd like to change? > Your signups go through your own form, and the free trial starts right after it. I'll check each signup before the trial starts. Someone who keeps making accounts gets a short message pointing them to support. If Heedeo is ever down, no one is refused: at most a signup goes through your own verification first. > > Should refused people write to support@acme.com? I'll use that unless you say otherwise. > > Anything you'd like to change? ## 4. Build Keep the app's stack, sign-in, routes and middleware as they are. 1. Keys: if `HEEDEO_SECRET_KEY` is not in the app's environment or .env.local, connect the app, where it lives: Start, and read `device_code`, `user_code` and `verification_url` from the answer: ```sh curl -s -X POST https://app.heedeo.com/v1/cli/start -H 'content-type: application/json' -d '{"name":""}' ``` Now stop, before any other work. Make the link and the code your whole reply, say the code lasts 5 minutes, ask them to tell you when they've connected, and end your turn. Only a reply is sure to reach them: a link in a tool's output or a passing note is not. They sign in and type the code. When they reply, poll. This writes both keys to .env.local, with the address of a Heedeo other than app.heedeo.com, and prints only the workspace's name, so the secret never reaches this chat. It prints `authorization_pending` until they have connected: wait 10 seconds and try again, for up to 5 minutes. If it prints `slow_down`, wait the seconds it names first, because polling too soon makes the wait longer. `expired_token` means the code ran out: start again and reply with the new link and code. ```sh curl -s -X POST https://app.heedeo.com/v1/cli/poll -H 'content-type: application/json' -d '{"device_code":""}' | node -e ' const fs = require("fs"); let s = ""; process.stdin.on("data", (d) => (s += d)).on("end", () => { const r = JSON.parse(s); if (!r.env) return console.log(r.error + (r.interval ? ": wait " + r.interval + " seconds" : "")); const api = "", env = { ...r.env, ...(api ? { HEEDEO_API_URL: api } : {}) }; const f = ".env.local", old = fs.existsSync(f) ? fs.readFileSync(f, "utf8").trimEnd() : ""; const kept = old ? old.split("\n").filter((l) => ![...Object.keys(env), "HEEDEO_API_URL"].some((k) => l.startsWith(k + "="))) : []; const lines = Object.entries(env).map(([k, v]) => k + "=" + v); fs.writeFileSync(f, [...kept, ...lines].join("\n") + "\n", { mode: 0o600 }); console.log("Connected to " + r.workspace); if (r.endpoint) console.log("Endpoint: " + r.endpoint); });' ``` Make sure .gitignore covers .env.local, and that every server that calls Heedeo reads it. In a Vite app without Next.js, rename `NEXT_PUBLIC_HEEDEO_KEY` to `VITE_HEEDEO_KEY`. Never print the secret key, commit it, ask for it in chat or put it in browser code. 2. Install `@heedeo/sdk` with the app's package manager. It owns script loading, forwarding, retries and timeouts: do not rewrite those. It runs on Node.js 20.18 or later, the Next.js App Router and Cloudflare Workers. On any other stack, follow the guide's manual sections. 3. Serve Heedeo from the app's own domain. If this text or the poll names an endpoint from the Domains page, pass it as `endpoint` to `createHeedeo` and add no route. Otherwise add the package's proxy route, reachable without sign-in. On an API on another origin, pass that route as `endpoint` and put it under the API's credentialed CORS. 4. In the browser, call `identify()` just before the call that carries its token to the server, not on page load, and send the token in that call's body. A token is good for one event. With a hosted sign-in, do this at each place from step 1, once the provider's session exists. If it fails, make the call without a token: the server still sends the event. 5. On the server, once per attempt at each place from step 1, call `heedeo.evaluate()` where the rule runs and `heedeo.logEvent()` everywhere else (a `login` with no rule on it). Both record the action and Heedeo flags it the same way; only `evaluate()` answers. Name each action from Heedeo's list, spelled exactly: `signup` where the account is new, `login` on later sign-ins if that call runs on every sign-in, and at the grant `trial_start`, `redeem:CODE` for credits or a coupon, or `payout`. The name is how Heedeo knows what the action means: never send `once`. Send the email, `userId`, and `evidence: { email: { verified } }` as the sign-in provider reports it. An app that never verifies an address leaves `evidence` out: `verified: false` on every signup adds to the score of each new address. In a server action pass `headers: await headers()` and `path` in place of `request`. Do not write your own fetch to `/v1/evaluations` or `/v1/events` when the package runs on this stack. 6. The client IP must come from the source the host documents as trusted: the package's `clientIp` option, the same function for the route and the event. If the repo does not show the host, make that one of your questions; do not guess a header. A fixed `127.0.0.1` is for development only: in production return `null` until you know. 7. Fail open. A network error, a timeout, a 5xx or a missing key mean: carry on as if Heedeo were not there. `data` is null whenever Heedeo did not answer, and that is never a refusal. No usable receipt is different: none (`no_token` in `reasons`: with no token the package still sends the event), one Heedeo refused (`api_error` with a 4xx `status`, such as 400 `invalid_token`), or `token_expired` or `unknown_visitor` in `reasons`. On the score and rules paths, require the app's own verification step (a confirmed email or a CAPTCHA) for it, if it has one, before the account is created or the grant is made; a script can send a made-up token. Under watch, or when the app has no verification step, log it and carry on: never add one. During a Heedeo outage, that means every signup goes through that step, never a refusal. 8. Run the rule before the account is created. With a hosted sign-in the account already exists, so run it at the grant. When it refuses, create or give nothing, and show one generic message that says how to reach support. An end user never sees a reason or a score. A sign-in is not judged: a rule on a later sign-in reads `data.account` (how many browsers present the account, and whether this one is new), never the score. 9. Write one server log line per attempt: the `event_id`, the visitor's id, the app's own user id, whether it was refused, `listed`, and `decision` and `risks` as JSON. ## 5. Prove Report what you saw, not what should happen. - Run the app's type checks and build. - Sign up in a real browser, through each sign-in path, and go on to the grant. The log line must show an `event_id` and a visitor id. A script that loads, or an `invalid_token` from a test call, proves nothing. - Read the workspace's counts, which keeps the secret out of the output: `curl -s -H "authorization: Bearer $(grep '^HEEDEO_SECRET_KEY=' .env.local | cut -d= -f2)" https://app.heedeo.com/v1/setup-status`. Identifications and events must each be above 0, last seen during your test. - Signup and the grant still work with the script blocked, and with Heedeo unreachable. - The rule does what the customer agreed to, by the guide's "Test your rule". ## 6. Hand over Finish with: the files you changed, the checks you ran and their results, one test `event_id`, the rule as you wrote it, what the customer still has to do to deploy (both keys, the client IP source, one signup over HTTPS), and the privacy-policy wording from the guide's section 7, cut down to what this app sends. Do not deploy unless told to. Say plainly what you could not verify.