> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heretic.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Signup rules

> Block repeat signups on the address and the identifiers before spending a challenge.

A probe is cheap and answers most cases. A challenge costs more. On signup, read the verdict, block the visitors you have seen before, and challenge only the rest.

## The fields

| Field               | A match means                                                                                            |
| ------------------- | -------------------------------------------------------------------------------------------------------- |
| `record.client_ip`  | Same network address.                                                                                    |
| `identity.machine`  | Same hardware in the same timezone, in any browser. Identical stock devices share it.                    |
| `identity.instance` | Same browser on the same hardware.                                                                       |
| `verdict`           | `contradicted`: the browser's claims conflict with its measurements. `refused`: it withheld measurement. |

The same `client_ip` and the same `machine` together is the same device on the same network. The same `instance` is the same browser. Identifiers are salted per site key, so compare them within one site and one `identity.version`. Heretic keeps no index of them. Store them with each account.

## Store the evidence

```sql theme={"dark"}
CREATE TABLE signup_evidence (
  account_id   TEXT PRIMARY KEY,
  client_ip    TEXT NOT NULL,
  instance_id  TEXT,
  machine_id   TEXT,
  identity_ver INTEGER NOT NULL
);
CREATE INDEX signup_evidence_ip       ON signup_evidence (client_ip);
CREATE INDEX signup_evidence_instance ON signup_evidence (instance_id);
CREATE INDEX signup_evidence_machine  ON signup_evidence (machine_id);
```

## The rule

```js theme={"dark"}
export async function signup(body, db) {
  const verdict = await readHereticVerdict(body.heretic_request_id); // see the verdict endpoint page
  if (!verdict) return reject('not-measured');

  const ip = verdict.record.client_ip;
  const { instance = null, machine = null, version } = verdict.identity;

  // Seen before: same device on the same network, or the same browser.
  const seen = await db.get(
    `SELECT account_id FROM signup_evidence
      WHERE identity_ver = ?
        AND ((client_ip = ? AND machine_id = ?) OR instance_id = ?)
      LIMIT 1`,
    version, ip, machine, instance,
  );
  if (seen) return reject('duplicate:' + seen.account_id);

  // Not seen, but the browser is lying or hiding: challenge, or reject.
  if (verdict.verdict !== 'uncontradicted') return challenge(body, db);

  const account = await createAccount(body, db);
  await db.run(
    `INSERT INTO signup_evidence VALUES (?, ?, ?, ?, ?)`,
    account.id, ip, instance, machine, version,
  );
  return { account_id: account.id };
}
```

The order is the point. A repeat device is rejected before a ceremony is minted. `challenge` mints one at [`POST /v1/challenge`](/api/challenge-endpoint) and returns its URL; the page opens it with `heretic.challenge`.

Variants: allow N accounts per `machine`, match `client_ip` alone, or match `network.asn` to cover a whole network.

## The page

```js theme={"dark"}
const probe = heretic({ siteKey: 'hrtc_live_0123456789abcdef01234567' });

form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const result = await probe;
  const next = await fetch('/signup', {
    method: 'POST',
    body: JSON.stringify({ ...fields, heretic_request_id: result.requestId }),
  }).then((r) => r.json());
  if (next.challenge_url) {
    heretic.challenge(next.challenge_url, {
      callback: (r) => { location.href = '/signup/finish?challenge=' + r.challenge_id; },
    });
  }
});
```

The probe starts on page load and usually finishes before the form does. Awaiting it at submit costs nothing once it is done. `heretic.challenge` comes from `guard.js`; see [show the ceremony](/api/challenge-endpoint#show-the-ceremony).

## While the probe runs

| Pattern             | How                                                                                                       |
| ------------------- | --------------------------------------------------------------------------------------------------------- |
| Wait                | Keep submit disabled until `result.requestId` exists.                                                     |
| Submit, then finish | Create the account as pending. Run the rule when the request ID arrives.                                  |
| Probe earlier       | Run the probe on a previous page and carry the request ID. Ordinary results stay readable for seven days. |

A `503` from the verdict endpoint means Heretic cannot say whether the result exists. Treat it as not measured, never as `uncontradicted`. A session that never completed reads `404` for 15 seconds, then `refused`.


## Related topics

- [Heretic documentation](/index.md)
- [Quickstart](/quickstart.md)
- [Verdicts and coverage](/concepts/verdicts-and-coverage.md)
- [Challenge and usage endpoints](/api/challenge-endpoint.md)
- [Verdict schema](/api/verdict-schema.md)
