> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.portreeve.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.portreeve.com/_mcp/server.

# Test mode and feedback

## Test mode

1. Integrate with `sk_test_` / `pk_test_` keys.
2. Fire each event type you use at least once (the
   [canonical payloads](/integration/event-payloads) are fine).
3. In the dashboard, flip the mode toggle to **Test** and confirm the events
   appear with verdicts and reasons.
4. Trigger your `block` path deliberately — add a blocklist entry in
   **Settings → Lists** (your own email domain works) — and confirm your UX
   shows the generic rejection.
5. Swap the env var to live keys. Nothing else changes.

Test-mode data is fully isolated — test velocity counters and clusters never
touch live decisions.

## Reporting outcomes back (feedback)

Feedback is what makes the identity graph sharp for you specifically:


```ts
import { Portreeve } from "portreeve";

const portreeve = new Portreeve(process.env.PORTREEVE_SECRET_KEY!);

// An allowed user turned out abusive — marks their whole linked cluster.
// An event-id string is shorthand for { kind: "event_id", value }.
await portreeve.feedback("evt_01J8Z3K9", "confirmed_abuse", "abuse ring confirmed by support");

// A dispute webhook holds a user id, not an event id — address the ACCOUNT.
await portreeve.feedback({ kind: "external_user_id", value: "usr_9f2c" }, "chargeback", "stripe dispute du_1QxT");

// A flagged user turned out real — the false-positive metric we track weekly.
await portreeve.feedback("evt_01J8Z3KA", "false_positive");
```

- `confirmed_abuse` marks the user's whole linked cluster; future linked
  accounts get blocked.
- `chargeback` has the **same cluster effect** as `confirmed_abuse` — a
  chargeback is ground-truth abuse. The split is about evidence source, so
  wire it mechanically: send `chargeback` from your payment provider's dispute
  webhook (Stripe `charge.dispute.created`) addressed to the **account**
  (`{ kind: "external_user_id", value: userId }` — no event id needed), and
  `confirmed_abuse` when a human or another system confirms abuse.
- `false_positive` is the metric we hold ourselves to weekly — send it even
  when it feels minor.
- **Safe to retry — no idempotency key needed.** The SDK retries once on a 5xx
  or network failure, and the API deduplicates on `(subject, outcome)`:
  reposting the same outcome returns `{"ok": true}` without recording a second
  row or double-counting the cluster mark. Replay whole batches freely. The
  dedupe key is the *outcome*, not the call — sending `false_positive` for a
  subject you earlier reported as `confirmed_abuse` is new, distinct feedback.
- If identity linking is still settling or the abuse mark cannot be written,
  the API returns `503` and does not acknowledge success. Retry the same feedback
  from a durable job until it succeeds; alert on persistent failures.
- Unlike `verdict()`, `feedback()` **throws** on failure rather than degrading —
  silently losing abuse feedback would be worse than an exception.

## Next

- [Handling verdicts](/reference/handling-verdicts) — the review flow, webhooks, checkout patterns.
- [Reason codes](/reference/reason-codes) — what every code means and what to do about it.