Skip to navigation

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 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:

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