Report outcomes
Tell AbuseZero what you actually granted, so every later evaluation checks against what really happened.
An outcome records what happened after an allow: whether you granted the benefit, didn't, or took it back. Outcomes become the history that every later evaluation is checked against, so later decisions rest on what really happened. For the full model, see Outcomes.
Before you begin
- A benefit and a server that evaluates claims. See Protect a benefit end to end.
- A server key with the scopes
outcomes:write,outcomes:readandbenefits:write.
Choose how the benefit tracks outcomes
Two benefit settings decide how outcomes are recorded. Set them when you create the benefit, or change them on the Benefits page or with PATCH /v1/benefits/{id}. Both are part of the benefit's configuration.
| Setting | Value | Effect |
|---|---|---|
outcome_tracking | assume_granted (default) | Every allowed claim counts as granted straight away. Its outcome starts as assumed_granted (see outcome states). You report only exceptions. |
report_outcome | Every allowed claim waits for your report. Its outcome starts as awaiting_outcome, with a due_at time. | |
missing_outcome_behavior | hold_eligibility (default) | If no report arrives by due_at, the outcome becomes outcome_unknown and still counts as claimed. |
release_eligibility | If no report arrives by due_at, the outcome becomes outcome_unknown and stops counting, so the user can claim again. |
Use report_outcome when granting can fail after the evaluation, for example when a trial needs a card check. Use assume_granted when an allow always leads to a grant.
Report a granted benefit
After you grant the benefit, report granted before outcome.due_at, with the SDK's outcome() or POST /v1/evaluations/{id}/outcome. The AbuseZero CLI command is abusezero outcomes report, and the AbuseZero MCP server tool is abusezero_report_outcome. Add external_outcome_id if you want to link the outcome to your own record.
import { abusezero } from "./lib/abusezero";
const report = await abusezero.outcome(evaluationId, {
outcome: "granted",
external_outcome_id: "trial_8f2d41",
}, `outcome:${evaluationId}:granted`);{
"object": "outcome_report",
"evaluation": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"state": "granted",
"conflicts": []
}Reporting the same outcome again changes nothing, so a retry with the same Idempotency-Key is always safe.
Report a benefit you didn't grant
If the claim was allowed but you didn't grant the benefit, report not_granted with a reason of 3 to 500 characters. The claim stops counting, so the user can claim the benefit again.
await abusezero.outcome(evaluationId, {
outcome: "not_granted",
reason: "Card check failed before the trial started.",
}, `outcome:${evaluationId}:not_granted`);Report a reversal
If you granted the benefit and later took it back, for example with a refund, report reversed with a reason. A reversed benefit still counts as received.
await abusezero.outcome(evaluationId, {
outcome: "reversed",
reason: "Refunded at the customer's request.",
}, `outcome:${evaluationId}:reversed`);Each report moves the outcome from its current state:
| Report | Accepted when the outcome is |
|---|---|
granted | awaiting_outcome, outcome_unknown or assumed_granted |
not_granted | awaiting_outcome, outcome_unknown or assumed_granted |
reversed | granted or assumed_granted |
Any other change returns OUTCOME_CONFLICT. Only allowed evaluations have outcomes, so a report for a denied evaluation also returns OUTCOME_CONFLICT. Allowed changes lists every transition.
Check for conflicts
A granted report can arrive after due_at on a benefit set to release_eligibility. By then, the same user may have claimed again. When that happens, conflicts lists the outcomes (out_…) recorded meanwhile for the same identifiers.
if (report.conflicts.length > 0) {
await flagForReview(evaluationId, report.conflicts);
}flagForReview stands for your own review process. Review each listed outcome and report reversed or not_granted where the benefit shouldn't stand.
Verify your integration
Read the outcomes for your workspace with GET /v1/outcomes, abusezero outcomes list in the AbuseZero CLI, or abusezero_list_outcomes on the AbuseZero MCP server. Outcomes still in awaiting_outcome near their due_at, or already in outcome_unknown, mean reports aren't arriving.
const outcomes = await abusezero.outcomes({ limit: 20 });{
"object": "list",
"data": [
{
"object": "outcome",
"id": "out_9a7b6c5d-4e3f-4a2b-8c1d-0e9f8a7b6c5d",
"evaluation": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"state": "granted",
"source": "evaluation",
"occurred_at": "2026-10-29T15:01:12.000Z",
"due_at": "2026-10-29T15:10:00.000Z"
}
],
"next_cursor": null
}How is this guide?
Last updated on